diff --git a/.azuredevops/policies/approvercountpolicy.yml b/.azuredevops/policies/approvercountpolicy.yml new file mode 100644 index 0000000000..78e0c2aa0f --- /dev/null +++ b/.azuredevops/policies/approvercountpolicy.yml @@ -0,0 +1,29 @@ +# This file configures the Approver Count Policy Validator status check. Some +# settings here are similar to branch policies specified via the Azure DevOps UI +# that govern how PRs are approved in general. The settings here dictate how +# the validator behaves, and it can also prevent PRs from completing. +# +# Suggested by Merlinbot (https://sqlclientdrivers.visualstudio.com/ADO.Net/_git/dotnet-sqlclient/pullrequest/4982) + +name: approver_count +description: Approver count policy for dotnet-sqlclient +resource: repository +where: +configuration: + approverCountPolicySettings: + isBlocking: true + requireMinimumApproverCount: 2 + creatorVoteCounts: false + allowDownvotes: false + sourcePushOptions: + resetOnSourcePush: false + requireVoteOnLastIteration: true + requireVoteOnEachIteration: false + resetRejectionsOnSourcePush: false + blockLastPusherVote: true + branchNames: + - refs/heads/internal/main + - refs/heads/internal/release/6.0 + - refs/heads/internal/release/5.2 + - refs/heads/internal/release/5.1 + displayName: dotnet-sqlclient Approver Count Policy diff --git a/.config/CredScanSuppressions.json b/.config/CredScanSuppressions.json index ffc7e87789..1bac9b0d92 100644 --- a/.config/CredScanSuppressions.json +++ b/.config/CredScanSuppressions.json @@ -1,10 +1,6 @@ { "tool": "Credential Scanner", "suppressions": [ - { - "file": "src/Microsoft.Data.SqlClient/tests/Docker/DockerLinuxTest/Program.cs", - "justification": "Test projects should be skipped" - }, { "file": "src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TDSServerArguments.cs", "justification": "Test projects should be skipped" @@ -13,10 +9,6 @@ "file": "src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TdsServerCertificate.pfx", "justification": "Test projects should be skipped" }, - { - "file": "src/docker-compose.yml", - "justification": "Docker test project should be excluded" - }, { "file": "doc/samples/SqlConnectionStringBuilder.cs", "justification": "Documentation could include sample data and can be ignored" diff --git a/.config/tsaoptions.json b/.config/tsaoptions.json index c9e3a2cfb6..077ef21c76 100644 --- a/.config/tsaoptions.json +++ b/.config/tsaoptions.json @@ -7,7 +7,7 @@ "repositoryName": "SqlClient", "codebaseName": "SqlClient", "allTools": true, - "template": "MSDATA_RevolutionR", + "template": "MSDATA_RevolutionR_Overloaded0", "language": "csharp", "includePathPatterns": "src/Microsoft.Data.SqlClient/*, src/Microsoft.SqlServer.Server/*, tools/*", "excludePathPatterns": "src/Microsoft.Data.SqlClient/tests/*" diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index e7b690f114..0000000000 --- a/.dockerignore +++ /dev/null @@ -1,25 +0,0 @@ -**/.classpath -**/.dockerignore -**/.env -**/.git -**/.gitignore -**/.project -**/.settings -**/.toolstarget -**/.vs -**/.vscode -**/*.*proj.user -**/*.dbmdl -**/*.jfm -**/azds.yaml -**/bin -**/charts -**/docker-compose* -**/Dockerfile* -**/node_modules -**/npm-debug.log -**/obj -**/secrets.dev.yaml -**/values.dev.yaml -LICENSE -README.md diff --git a/.editorconfig b/.editorconfig index b2014e3974..f0ea20ec32 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,7 +11,7 @@ insert_final_newline = true indent_style = space indent_size = 4 -[project.json] +[*.{json,jsonc}] indent_size = 2 # C# files @@ -132,7 +132,12 @@ csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false -# Xml project files +# Analyzers + +dotnet_code_quality.ca1802.api_surface = private, internal + +# CA2000: Dispose objects before losing scope +dotnet_diagnostic.CA2000.severity = suggestion # CA1063: Implement IDisposable Correctly dotnet_diagnostic.CA1063.severity = silent @@ -143,6 +148,10 @@ dotnet_diagnostic.CA2100.severity = silent # CA1416: Validate platform compatibility dotnet_diagnostic.CA1416.severity = silent +dotnet_code_quality.CA2100.excluded_type_names_with_derived_types = Microsoft.Data.SqlClient.ManualTesting.Tests.* +dotnet_diagnostic.xUnit1031.severity=none +dotnet_diagnostic.xUnit1030.severity=none + [*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}] indent_size = 2 @@ -163,13 +172,3 @@ indent_size = 2 end_of_line = lf [*.{cmd, bat}] end_of_line = crlf - -# Analyzers -dotnet_code_quality.ca1802.api_surface = private, internal - -[*.cs] -dotnet_code_quality.CA2100.excluded_type_names_with_derived_types = Microsoft.Data.SqlClient.ManualTesting.Tests.* -dotnet_diagnostic.xUnit1031.severity=none -dotnet_diagnostic.xUnit1030.severity=none - - diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..f355b2dd5e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,25 @@ +#============================================================================== +# This is the global GitHub code owners file for the SqlClient repo. No other +# CODEOWNERS files should exist in the repo. +# +# Code owners documentation is here: +# +# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners + +# The entire repo is owned by the SqlClientDevTeam GitHub team: +# +# https://github.com/orgs/dotnet/teams/sqlclientdevteam +# +# This team must be configured with write access to the repo for code ownership +# rules to apply. +# +# The main branch should also be protected and require all PRs to be approved +# by owners. See: +# +# https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-pull-request-reviews-before-merging +# +# There should be no other owners specified in the repo. +# +* @dotnet/sqlclientdevteam + +#============================================================================== diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 8e9cbe83f3..725af1ede9 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -1,9 +1,9 @@ --- name: Bug report about: Create a report to help us improve -title: '' -labels: 'Triage Needed' -assignees: '' +title: "" +labels: [":new: Triage Needed", ":bug: Bug!"] +assignees: [] --- @@ -31,9 +31,9 @@ A clear and concise description of what you expected to happen. ### Further technical details Microsoft.Data.SqlClient version: (found on the nuget or Microsoft.Data.SqlClient.dll) -.NET target: (e.g. Framework 4.7.1, Core 2.2.2) -SQL Server version: (e.g. SQL Server 2017) -Operating system: (e.g. Windows 2019, Ubuntu 18.04, macOS 10.13, Docker container) +.NET target: (e.g. .NET Framework 4.7.2, .NET 6.0) +SQL Server version: (e.g. SQL Server 2022) +Operating system: (e.g. Windows Server 2022, Ubuntu 24.04, macOS 14.7.1, Docker container) **Additional context** -Add any other context about the problem here. +Add any other context about the problem here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..51115e1c26 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: SqlClient Community Support / Discussions + url: https://github.com/dotnet/SqlClient/discussions + about: Please ask and answer questions here. + - name: MSRC Vulnerability Reporting / Bug Bounty + url: https://msrc.microsoft.com/create-report + about: Please report security vulnerabilities here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 35dfeec7ab..db9a33148c 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,9 +1,9 @@ --- name: Feature request about: Suggest an idea for this project -title: '' -labels: 'Triage Needed' -assignees: '' +title: "" +labels: [":new: Triage Needed", ":bulb: Enhancement"] +assignees: [] --- @@ -17,4 +17,4 @@ A clear and concise description of what you want to happen. A clear and concise description of any alternative solutions or features you've considered. ### Additional context -Add any other context about the feature request here. +Add any other context about the feature request here. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md index 1e758f1390..36e64c2267 100644 --- a/.github/ISSUE_TEMPLATE/other.md +++ b/.github/ISSUE_TEMPLATE/other.md @@ -1,10 +1,8 @@ --- name: Other about: Ask a question or file a different type of issue -title: '' -labels: 'Triage Needed' -assignees: '' - ---- - +title: "" +labels: [":new: Triage Needed"] +assignees: [] +--- \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000000..3cbf88a9a9 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,146 @@ +# GitHub Copilot Instructions for dotnet/SqlClient + +## 🧑‍💻 Roles and Responsibilities +- **Triage**: Review new issues, categorize them, and assign them to appropriate team members. +- **Fixing Issues**: Address bugs, implement feature requests, and complete tasks as assigned. +- **Writing Tests**: Ensure all changes are covered by unit tests and integration tests. +- **Documentation**: Update documentation for public APIs, features, and usage examples. +- **Pull Requests**: Create and submit PRs for review, ensuring they follow project conventions and include necessary tests and documentation. +- **Code Reviews**: Review PRs from other contributors, providing feedback and suggestions for improvements. +- **Continuous Improvement**: Identify areas for improvement in the code base, documentation, and processes, and implement changes to enhance the project. + +## 📚 Project Overview +This project is a .NET data provider for SQL Server, enabling .NET applications to interact with SQL Server databases. It supports various features like connection pooling, transaction management, and asynchronous operations. +The project is structured to support both .NET Framework and .NET Core, with platform-specific implementations in `netfx/` and `netcore/` directories. +The project includes: +- **Public APIs**: Defined in `ref/` directories for both .NET Framework and .NET Core. +- **Implementations**: Actual code in `src/` directories for both platforms. +- **Tests**: Located in the `tests/` directory, covering unit and integration tests. + - **Unit Tests**: Located in `tests/UnitTests/` directory, which includes tests for individual components and methods. + - **Functional Tests**: Located in `tests/FunctionalTests/` directory, which includes tests for various features and functionalities that can be run without a SQL Server instance. + - **Manual Tests**: Located in `tests/ManualTests/` directory, which includes tests that require a SQL Server instance to run. +- **Documentation**: Found in the `doc/` directory, including API documentation, usage examples. +- **Policies**: Contribution guidelines, coding standards, and review policies in the `policy/` directory. +- **Building**: The project uses MSBuild for building and testing, with configurations and targets defined in the `build.proj` file, whereas instructions are provided in the `BUILDGUIDE.md` file. +- **CI/CD**: ADO Pipelines for CI/CD and Pull request validation are defined in the `eng/` directory, ensuring code quality and automated testing. + +## 📦 Products +This project includes several key products and libraries that facilitate SQL Server connectivity and data access: +- **Microsoft.Data.SqlClient**: The primary library for SQL Server data access, providing a rich set of APIs for connecting to SQL Server databases, executing commands, and retrieving data. +- **Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider**: Enables Always Encrypted with Azure Key Vault integration, allowing SQL Server column encryption and decryption using keys managed in Azure Key Vault. +- **Microsoft.SqlServer.Server**: Supplies APIs for SQL Server-specific features, including user-defined types (UDTs) and SQL Server-specific attributes. + +## 🛠️ Key Features +- **Connectivity to SQL Server**: Provides robust and secure connections to SQL Server databases, using various authentication methods, such as Windows Authentication, SQL Server Authentication, and Azure Active Directory authentication, e.g. `ActiveDirectoryIntegrated`, `ActiveDirectoryPassword`, `ActiveDirectoryServicePrincipal`,`ActiveDirectoryInteractive`, `ActiveDirectoryDefault`, and `ActiveDirectoryManagedIdentity`. +- **Connection Resiliency**: Implements connection resiliency features to handle transient faults and network issues, ensuring reliable database connectivity. +- **TLS Encryption**: Supports secure connections using TLS protocols to encrypt data in transit. Supports TLS 1.2 and higher, ensuring secure communication with SQL Server. Supported encryption modes are: + - **Optional**: Encryption is used if available, but not required. + - **Mandatory**: Encryption is mandatory for the connection. + - **Strict**: Enforces strict TLS requirements, ensuring only secure connections are established. +- **TLS 1.3 Support**: Supports TLS 1.3 for enhanced security and performance in data transmission when connecting with 'Strict' encryption mode. +- **TDS Protocol**: Implements the Tabular Data Stream (TDS) protocol for communication with SQL Server, supported protocol versions include TDS 7.4 and 8.0. +- **Data Access**: Supports executing SQL commands, retrieving data using `SqlDataReader`, and managing data with `SqlCommand` and `SqlConnection` objects. +- **MultipleActiveResultSets (MARS)**: Supports Multiple Active Result Sets, allowing multiple active commands to be executed on a single connection. +- **Asynchronous Operations**: Supports async/await patterns for non-blocking database operations. +- **Connection Pooling**: Efficiently manages database connections in pools to improve performance. +- **Transaction Management**: Supports transaction management using local transaction management and Global transactions using MSDTC for data integrity and consistency. +- **Parameterization**: Supports prevention of SQL injection attacks by using parameterized queries. +- **Data Types**: Supports a wide range of SQL Server data types, including Json, Vector, custom types and UDTs. +- **Data Encryption**: Supports data encryption for secure data transmission. +- **Logging and Diagnostics**: Provides event source tracing diagnostic capabilities for troubleshooting. +- **Failover Support**: Handles automatic failover scenarios for high availability. +- **Cross-Platform Support**: Compatible with both .NET Framework and .NET Core, allowing applications to run on Windows, Linux, and macOS. +- **Column Encryption AKV Provider**: Supports Azure Key Vault (AKV) provider for acquiring keys from Azure Key Vault to be used for encryption and decryption. + +## 🧩 SNI Implementations +There are two implementations of the SQL Server Network Interface (SNI) layer used in this project: +- **Managed SNI**: A managed implementation of SNI that is used in .NET Core and .NET 5+ environments. It provides cross-platform support for SQL Server connectivity. +- **Native SNI**: A native implementation of SNI that is used in .NET Framework and .NET Core environments on Windows. It's shipped as part of the `Microsoft.Data.SqlClient.SNI` and `Microsoft.Data.SqlClient.SNI.Runtime` packages. + - **Microsoft.Data.SqlClient.SNI**: This package provides the native SNI layer for .NET Framework applications. + - **Microsoft.Data.SqlClient.SNI.Runtime**: This package provides the native SNI layer for .NET Core applications on Windows. + +## 📌 Instructions for Copilot + +### 🐛 Triage Issues +When a new issue is created, follow these steps: +1. **Acknowledge Receipt**: Respond within 48 hours to confirm the issue is being triaged. +2. **Set Issue Type**: + - Update issue type as `Bug`, `Feature`, `Task`, or `Epic` if not done already, based on: + - Use `Bug` for issues opened following `ISSUE_TEMPLATE\bug-report.md` template, and is complaining about an unexpected behavior. + - Use `Feature` for issues opened following `ISSUE_TEMPLATE\feature_request.md` template, containing proposals to incorporate. + - Use `Task` for issues opened as sub-issues. + - Use `Epic` for issues acting as a high-level work linked to multiple sub-issues. +3. **Request Missing Information**: If any required details are missing based on the issue template, ask the reporter to provide them. +4. **Labeling**: Apply appropriate labels like `Area\Engineering`, `Area\Json`, `Area\AKV Provider`, `Area\Connection Pooling`, etc. based on the issue content. + - Use `Triage Needed :new:` for new issues that need initial review. + - Use `Performance :chart_with_upwards_trend:` for issues that are targeted to performance improvements/concerns. + - Use `Needs more info :information_source:` for issues that require additional information. +5. **Link Related Issues**: If the issue is related to other issues, link them for better context. +6. **Add Comments**: As you triage issues, comment on the issue to: + - Confirm receipt and triage status. + - Ask for any missing information. + - Provide initial thoughts or questions to clarify the issue. + - Include links to relevant documentation or code examples. + - Use the `@` mention feature to notify relevant team members or stakeholders. + +### 🛠️ Fixing Issues +- Focus on issues assigned to you. +- For issues labeled `Needs more info :information_source:`, ask for clarifications or additional details. +- For issues labeled `Performance :chart_with_upwards_trend:`, prioritize performance-related improvements. +- Use the issue description to understand the problem and identify the root cause. + - If the issue is a bug, ensure you can reproduce it with the provided code sample. + - If the issue is a feature request, review the proposal and assess its feasibility. + - If the issue is a task, follow the instructions provided in the issue description. + - If the issue is an epic, break it down into smaller tasks or bugs and create sub-issues as needed. +- Cross-reference issue descriptions with code in `src/` folders, especially in `netfx/src/` and `netcore/src/`. +- If public APIs are changed, update corresponding `ref/` projects. +- Add or update tests in `tests/` to validate the fix. + +### 🧪 Writing Tests +- For every bug fix, ensure there are unit tests and manual (integration) tests that cover the scenario. +- For new features, write tests that validate the functionality. +- Use the existing test framework in the `tests/` directory. +- Follow the naming conventions and structure of existing tests. +- Ensure tests are comprehensive and cover edge cases. +- If the issue involves changes to public APIs, update the corresponding `ref/` projects to reflect those changes. +- Add sample code snippets in the 'doc/samples/' directory to demonstrate the new or fixed functionality and link them in the documentation in `doc/` folder. + +### 📝 Documentation +- All public documentation for APIs should be updated in the `doc/` directories. +- When adding or changing XML docs, ensure they are clear and follow the existing style. + +### 🔁 Creating Pull Requests +- Use the `Fixes #issue_number` syntax in the PR description to automatically close the issue when the PR is merged. +- Include a summary of the fix and link to the related issue +- Add `[x]` checkboxes for: + - [ ] Tests added or updated + - [ ] Public API changes documented + - [ ] Verified against customer repro (if applicable) + - [ ] Ensure no breaking changes introduced +- Ensure the PR passes all CI checks before merging. + +### ✅ Closing Issues +- Add a comment summarizing the fix and referencing the PR + +### ⚙️ Automating Workflows +- Auto-label PRs based on folder paths (e.g., changes in `netcore/src` → `area-netcore`) and whether they add new public APIs or introduce a breaking change. +- Suggest changelog entries for fixes in `CHANGELOG.md` +- Tag reviewers based on `CODEOWNERS` file + +## 🧠 Contextual Awareness +- Always check for platform-specific differences between `netfx` and `netcore` +- Respect API compatibility rules across .NET versions +- Do not introduce breaking changes without proper justification and documentation +- Use the `doc/` directory for any new documentation or updates to existing documentation +- Use the `tests/` directory for any new tests or updates to existing tests +- Use the `doc/samples/` directory for any new code samples or updates to existing samples +- Use the `policy/` directory for any new policies or updates to existing policies + +## Constraints +- Do not modify the `CODEOWNERS` file directly. +- Do not modify the `CHANGELOG.md` file directly. +- Do not close issues without a fix or without providing a clear reason. + +## 📝 Notes +- Update policies and guidelines in the `policy/` directory as needed based on trending practices and team feedback. +- Regularly review and update the `doc/` directory to ensure it reflects the current state of the project. \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..d79ffba337 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,34 @@ +## Description + +Provide a summary of the changes being introduced. Important topics to cover +include: + +- Description of the functionality. +- API changes, backwards compatibility, deprecations, etc. +- Documentation, localization. +- Bug fixes. +- Code hygiene, refactoring, improvements. +- Engineering processes (CI, pipelines, test coverage) + +High quality descriptions will lead to a smoother review experience. + +## Issues + +Link to any relevant issues, bugs, or discussions (e.g., "Closes \#123", "Fixes +issue \#456"). + +## Testing + +Describe the automated tests (unit, integration) you created or modified. +Provide justification for any gap in automated testing. List any manual testing +steps that were performed to ensure the changes work.  + +## Guidelines + +Please review the contribution guidelines before submitting a pull request: + +- [Contributing](/CONTRIBUTING.md) +- [Code of Conduct](/CODE_OF_CONDUCT.md) +- [Best Practices](/policy/coding-best-practices.md) +- [Coding Style](/policy/coding-style.md) +- [Review Process](/policy/review-process.md) diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index 45483b0f2d..aa334124b3 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -30,6 +30,7 @@ Once the environment is setup properly, execute the desired set of commands belo |`RunTests`|Runs the functional and manual 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 @@ -288,26 +289,6 @@ When connecting to a server, if a protocol lower than TLS 1.2 is negotiated, a s `Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning` -## Debugging SqlClient on Linux from Windows - -For enhanced developer experience, we support debugging SqlClient on Linux from Windows, using the project "**Microsoft.Data.SqlClient.DockerLinuxTest**" that requires "Container Tools" to be enabled in Visual Studio. You may import configuration: [VS19Components.vsconfig](./tools/vsconfig/VS19Components.vsconfig) if not enabled already. - -This project is also included in `docker-compose.yml` to demonstrate connectivity with SQL Server docker image. - -To run the same: - -1. Build the Solution in Visual Studio -2. Set `docker-compose` as Startup Project -3. Run "Docker-Compose" launch configuration. -4. You will see similar message in Debug window: - - ```log - Connected to SQL Server v15.00.4023 from Unix 4.19.76.0 - The program 'dotnet' has exited with code 0 (0x0). - ``` - -5. Now you can write code in [Program.cs](/src/Microsoft.Data.SqlClient/tests/DockerLinuxTest/Program.cs) to debug SqlClient on Linux! - ### Troubleshooting Docker issues There may be times where connection cannot be made to SQL Server, we found below ideas helpful: @@ -338,14 +319,118 @@ dotnet test --collect:"XPlat Code Coverage" ## Run Performance Tests -### Running Performance test project directly +The performance tests live here: +`src\Microsoft.Data.SqlClient\tests\PerformanceTests\` + +They can be run from the command line by following the instructions below. + +Launch a shell and change into the project directory: + +PowerShell: + +```pwsh +> cd src\Microsoft.Data.SqlClient\tests\PerformanceTests +``` + +Bash: + +```bash +$ cd src/Microsoft.Data.SqlClient/tests/PerformanceTests +``` + +### Create Database + +Create an empty database for the benchmarks to use. This example assumes +a local SQL server instance using SQL authentication: + +```bash +$ sqlcmd -S localhost -U sa -P password +1> create database [sqlclient-perf-db] +2> go +1> quit +``` + +The default `runnerconfig.json` expects a database named `sqlclient-perf-db`, +but you may change the config to use any existing database. All tables in +the database will be dropped when running the benchmarks. + +### Configure Runner + +Configure the benchmarks by editing the `runnerconfig.json` file directly in the +`PerformanceTests` directory with an appropriate connection string and benchmark +settings: + +```json +{ + "ConnectionString": "Server=tcp:localhost; Integrated Security=true; Initial Catalog=sqlclient-perf-db;", + "UseManagedSniOnWindows": false, + "Benchmarks": + { + "SqlConnectionRunnerConfig": + { + "Enabled": true, + "LaunchCount": 1, + "IterationCount": 50, + "InvocationCount":30, + "WarmupCount": 5, + "RowCount": 0 + }, + ... + } +} +``` + +Individual benchmarks may be enabled or disabled, and each has several +benchmarking options for fine tuning. + +After making edits to `runnerconfig.json` you must perform a build which will +copy the file into the `artifacts` directory alongside the benchmark DLL. By +default, the benchmarks look for `runnerconfig.json` in the same directory as +the DLL. + +Optionally, to avoid polluting your git workspace and requring a build after +each config change, copy `runnerconfig.json` to a new file, make your edits +there, and then specify the new file with the RUNNER_CONFIG environment +variable. + +PowerShell: -Project location from Root: `src\Microsoft.Data.SqlClient\tests\PerformanceTests\Microsoft.Data.SqlClient.PerformanceTests.csproj` -Configure `runnerconfig.json` file with connection string and preferred settings to run Benchmark Jobs. +```pwsh +> copy runnerconfig.json $HOME\.configs\runnerconfig.json + +# Make edits to $HOME\.configs\runnerconfig.json + +# You must set the RUNNER_CONFIG environment variable for the current shell. +> $env:RUNNER_CONFIG="${HOME}\.configs\runnerconfig.json" +``` + +Bash: ```bash -cd src\Microsoft.Data.SqlClient\tests\PerformanceTests -dotnet run -c Release -f net8.0 +$ cp runnerconfig.json ~/.configs/runnerconfig.json + +# Make edits to ~/.configs/runnerconfig.json + +# Optionally export RUNNER_CONFIG. +$ export RUNNER_CONFIG=~/.configs/runnerconfig.json +``` + +### Run Benchmarks + +All benchmarks must be compiled and run in **Release** configuration. + +PowerShell: + +```pwsh +> dotnet run -c Release -f net9.0 ``` -_Only "**Release** Configuration" applies to Performance Tests_ +Bash: + +```bash +# Omit RUNNER_CONFIG if you exported it earlier, or if you're using the +# copy prepared by the build. +$ dotnet run -c Release -f net9.0 + +$ RUNNER_CONFIG=~/.configs/runnerconfig.json dotnet run -c Release -f net9.0 +``` diff --git a/CHANGELOG.md b/CHANGELOG.md index 736b24794a..a53ceb250c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,72 @@ 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/) +# Release Notes + +## [Stable release 6.0.2] - 2025-04-25 + +This update brings the below changes over the previous release: + +### Fixed + +- Fixed possible `NullPointerException` during socket receive [#3283](https://github.com/dotnet/SqlClient/pull/3283) +- Fixed reference assembly definitions for SqlJson APIs [#3169](https://github.com/dotnet/SqlClient/pull/3169) +- Fixed an error reading the output parameter of type JSON while executing stored procedure [#3173](https://github.com/dotnet/SqlClient/pull/3173) + +### Changed +- Updated the below dependencies: + - Updated [Microsoft.Bcl.Cryptography](https://www.nuget.org/packages/Microsoft.Bcl.Cryptography/9.0.4) from 9.0.0 to 9.0.4 for .NET 9 targeted dll. [#3281](https://github.com/dotnet/SqlClient/pull/3281) + - Updated [Microsoft.Extensions.Caching.Memory](https://www.nuget.org/packages/Microsoft.Extensions.Caching.Memory/9.0.4) from 9.0.0 to 9.0.4 for .NET 9 targeted dll. [#3281](https://github.com/dotnet/SqlClient/pull/3281) + - Updated [System.Configuration.ConfigurationManager](https://www.nuget.org/packages/System.Configuration.ConfigurationManager/9.0.4) from 9.0.0 to 9.0.4 for .NET 9 targeted dll. [#3281](https://github.com/dotnet/SqlClient/pull/3281) + - Updated [System.Security.Cryptography.Pkcs](https://www.nuget.org/packages/System.Security.Cryptography.Pkcs/9.0.4) from 9.0.0 to 9.0.4 for .NET 9 targeted dll. [#3281](https://github.com/dotnet/SqlClient/pull/3281) + +## [Stable release 6.0.1] - 2025-01-23 + +This update brings the below changes over the previous release: + +### Fixed + +- Fixed reference assembly definitions for SqlClientDiagnostic APIs [#3097](https://github.com/dotnet/SqlClient/pull/3097) +- Fixed issue with down-level SSL/TLS version warnings [#3126](https://github.com/dotnet/SqlClient/pull/3126) + +### Changed + +- Dependency changes + - Updated SNI dependency `Microsoft.Data.SqlClient.SNI` and `Microsoft.Data.SqlClient.SNI.runtime` to `6.0.2` [#3116](https://github.com/dotnet/SqlClient/pull/3116) [#3117](https://github.com/dotnet/SqlClient/pull/3117) + +## [Stable release 6.0.0] - 2024-12-09 + +_No changes since the last preview release_ + +## [Preview Release 6.0.0-preview3.24332.3] - 2024-11-27 + +This update brings the below changes over the previous release: + +### Breaking Changes +- Dropped support for .NET 6 [#2927](https://github.com/dotnet/SqlClient/pull/2927) +- Removed SQL 2000 client-side debugging support for .NET Framework [#2981](https://github.com/dotnet/SqlClient/pull/2981), [#2940](https://github.com/dotnet/SqlClient/pull/2940) + +### Added +- Enabled NuGet package auditing via NuGet.org audit source [#3024](https://github.com/dotnet/SqlClient/pull/3024) +- Added support for .NET 9 [#2946](https://github.com/dotnet/SqlClient/pull/2946) +- Added dependency on System.Security.Cryptography.Pkcs:9.0.0 to address [SYSLIB0057](https://learn.microsoft.com/en-us/dotnet/fundamentals/syslib-diagnostics/syslib0057)[#2946](https://github.com/dotnet/SqlClient/pull/2946) +- Added dependency on Microsoft.Bcl.Cryptography:9.0.0 [#2946](https://github.com/dotnet/SqlClient/pull/2946) +- Added missing SqlCommand_BeginExecuteReader code sample [#3009](https://github.com/dotnet/SqlClient/pull/3009) +- Added support for SqlConnectionOverrides in OpenAsync() API [#2433](https://github.com/dotnet/SqlClient/pull/2433) +- Added localization in Czech, Polish, and Turkish [#2987](https://github.com/dotnet/SqlClient/pull/2987) + +### Fixed +- Reverted default value of UseMinimumLoginTimeout context switch to 'true' [#2419](https://github.com/dotnet/SqlClient/pull/2419) +- Added missing DynamicallyAccessedMembers attributes in .NET Runtime reference assemblies. [#2946](https://github.com/dotnet/SqlClient/pull/2946) +- Synchronized dependencies of Reference Assemblies with Runtime assemblies [#2878](https://github.com/dotnet/SqlClient/pull/2878) +- Fixed lazy initialization of the _SqlMetaData hidden column map for .NET Framework [#2964](https://github.com/dotnet/SqlClient/pull/2964) + +### Changed +- Updated Microsoft.Extensions.Caching.Memory to 9.0.0 for all frameworks [#2946](https://github.com/dotnet/SqlClient/pull/2946) +- Updated System.Configuration.ConfigurationManager to 9.0.0 [#2946](https://github.com/dotnet/SqlClient/pull/2946) +- Updated docs to use absolute links [#2949](https://github.com/dotnet/SqlClient/pull/2949) +- Removed System.Text.Json dependency from .NET 8 [#2930](https://github.com/dotnet/SqlClient/pull/2930) + ## [Preview Release 6.0.0-preview2.24304.8] - 2024-10-30 This update brings the below changes over the previous release: @@ -42,7 +108,7 @@ This update brings the below changes over the previous release: ### Added - Added `TokenCredential` object to take advantage of token caching in `ActiveDirectoryAuthenticationProvider`. [#2380](https://github.com/dotnet/SqlClient/pull/2380) -- Added `DateOnly` and `TimeOnly` support to `DataTable` as a structured parameter. [#2258](https://github.com/dotnet/SqlClient/pull/2258) +- Added support for using `DateOnly` and `TimeOnly` in `DataTable` and `SqlDataRecord` structured parameters. [#2258](https://github.com/dotnet/SqlClient/pull/2258) - Added `Microsoft.Data.SqlClient.Diagnostics.SqlClientDiagnostic` type in .NET. [#2226](https://github.com/dotnet/SqlClient/pull/2226) - Added scope trace for `GenerateSspiClientContext`. [#2497](https://github.com/dotnet/SqlClient/pull/2497), [#2725](https://github.com/dotnet/SqlClient/pull/2725) @@ -58,7 +124,7 @@ This update brings the below changes over the previous release: - Fixed clone of `SqlConnection` to include `AccessTokenCallback`. [#2525](https://github.com/dotnet/SqlClient/pull/2525) - Fixed issue with `DateTimeOffset` in table-valued parameters, which was introduced in 5.2. [#2453](https://github.com/dotnet/SqlClient/pull/2453) - Fixed `ArgumentNullException` on `SqlDataRecord.GetValue` when using user-defined data type on .NET. [#2448](https://github.com/dotnet/SqlClient/pull/2448) -- Fixed `SqlBuffer` and `SqlGuild` when it's null. [#2310](https://github.com/dotnet/SqlClient/pull/2310) +- Fixed `SqlBuffer` and `SqlGuid` when it's null. [#2310](https://github.com/dotnet/SqlClient/pull/2310) - Fixed `SqlBulkCopy.WriteToServer` state in a consecutive calls. [#2375](https://github.com/dotnet/SqlClient/pull/2375) - Fixed null reference exception with `SqlConnection.FireInfoMessageEventOnUserErrors` after introducing the batch command. [#2399](https://github.com/dotnet/SqlClient/pull/2399) @@ -81,6 +147,22 @@ This update brings the below changes over the previous release: - Updated assembly version to 6.0.0.0. [#2382](https://github.com/dotnet/SqlClient/pull/2382) - Code health improvements: [#2366](https://github.com/dotnet/SqlClient/pull/2366), [#2369](https://github.com/dotnet/SqlClient/pull/2369), [#2381](https://github.com/dotnet/SqlClient/pull/2381), [#2390](https://github.com/dotnet/SqlClient/pull/2390), [#2392](https://github.com/dotnet/SqlClient/pull/2392), [#2403](https://github.com/dotnet/SqlClient/pull/2403), [#2410](https://github.com/dotnet/SqlClient/pull/2410), [#2413](https://github.com/dotnet/SqlClient/pull/2413), [#2425](https://github.com/dotnet/SqlClient/pull/2425), [#2428](https://github.com/dotnet/SqlClient/pull/2428), [#2440](https://github.com/dotnet/SqlClient/pull/2440), [#2443](https://github.com/dotnet/SqlClient/pull/2443), [#2450](https://github.com/dotnet/SqlClient/pull/2450), [#2466](https://github.com/dotnet/SqlClient/pull/2466), [#2486](https://github.com/dotnet/SqlClient/pull/2486), [#2521](https://github.com/dotnet/SqlClient/pull/2521), [#2522](https://github.com/dotnet/SqlClient/pull/2522), [#2533](https://github.com/dotnet/SqlClient/pull/2533), [#2552](https://github.com/dotnet/SqlClient/pull/2552), [#2560](https://github.com/dotnet/SqlClient/pull/2560), [#2726](https://github.com/dotnet/SqlClient/pull/2726), [#2751](https://github.com/dotnet/SqlClient/pull/2751), [#2811](https://github.com/dotnet/SqlClient/pull/2811) +## [Stable release 5.2.3] - 2025-04-29 + +This update brings the following changes since the 5.2.2 release: + +### Fixed + +- Fixed possible `NullPointerException` during socket receive (PR [#3284](https://github.com/dotnet/SqlClient/pull/3284)) +- Fixed inconsistencies between source and reference projects (PR [#3124](https://github.com/dotnet/SqlClient/pull/3124)) +- Adjusted retry logic to allow errors with negative numbers to be considered transient (PR [#3185](https://github.com/dotnet/SqlClient/pull/3185)) + +### Changed + +- Updated the following dependencies: + - [System.Private.Uri](https://www.nuget.org/packages/System.Private.Uri) 4.3.2 - Avoid transitive [CVE-2019-0820](https://msrc.microsoft.com/update-guide/en-US/advisory/CVE-2019-0820) (PR [#3076](https://github.com/dotnet/SqlClient/pull/3076)) + - [Microsoft.Extensions.Caching.Memory](https://www.nuget.org/packages/Microsoft.Extensions.Caching.Memory/6.0.3) 6.0.1 to 6.0.3 - Avoid [CVE-2024-43483](https://github.com/advisories/GHSA-qj66-m88j-hmgj) (PR [#3280](https://github.com/dotnet/SqlClient/pull/3280)) + ## [Stable release 5.2.2] - 2024-08-27 ### Fixed @@ -310,6 +392,26 @@ This update brings the below changes over the previous release: - Added Microsoft.SqlServer.Types to verify support for SqlHierarchyId and Spatial for .NET Core. [#1848](https://github.com/dotnet/SqlClient/pull/1848) - Code health improvements:[#1943](https://github.com/dotnet/SqlClient/pull/1943)[#1949](https://github.com/dotnet/SqlClient/pull/1949)[#1198](https://github.com/dotnet/SqlClient/pull/1198)[#1829](https://github.com/dotnet/SqlClient/pull/1829) +## [Stable release 5.1.7] - 2025-04-25 + +This update brings the following changes since the 5.1.6 release: + +### Fixed + +- Fixed possible `NullPointerException` during socket receive (PR [#3285](https://github.com/dotnet/SqlClient/pull/3285)) +- Fixed inconsistencies between source and reference projects (PR [#3180](https://github.com/dotnet/SqlClient/pull/3180)) + +### Changed + +- Updated the following dependencies: + - [Microsoft.Data.SqlClient.SNI](https://www.nuget.org/packages/Microsoft.Data.SqlClient.SNI/5.1.2) 5.1.1 to 5.1.2 for .NET Framework on Windows (PR [#3294](https://github.com/dotnet/SqlClient/pull/3294)) + - [Microsoft.Data.SqlClient.SNI.runtime](https://www.nuget.org/packages/Microsoft.Data.SqlClient.SNI.runtime/5.1.2) 5.1.1 to 5.1.2 for .NET on Windows (PR [#3294](https://github.com/dotnet/SqlClient/pull/3294)) + - [Microsoft.Extensions.Caching.Memory](https://www.nuget.org/packages/Microsoft.Extensions.Caching.Memory/6.0.3) 6.0.1 to 6.0.3 - Avoid [CVE-2024-43483](https://github.com/advisories/GHSA-qj66-m88j-hmgj) (PR [#3068](https://github.com/dotnet/SqlClient/pull/3068)) + - [Microsoft.Extensions.Hosting](https://www.nuget.org/packages/Microsoft.Extensions.Hosting/6.0.1) 6.0.0 to 6.0.1 - Avoid transitive dependency on vulnerable [System.Text.Json](https://www.nuget.org/packages/System.Text.Json/6.0.0) 6.0.0 (PR [#3207](https://github.com/dotnet/SqlClient/pull/3207)) + - [System.Private.Uri](https://www.nuget.org/packages/System.Private.Uri) 4.3.2 - Avoid transitive [CVE-2019-0820](https://msrc.microsoft.com/update-guide/en-US/advisory/CVE-2019-0820) (PR [#3077](https://github.com/dotnet/SqlClient/pull/3077)) + - [System.Text.Encodings.Web](https://www.nuget.org/packages/System.Text.Encodings.Web/6.0.1) 6.0.0 to 6.0.1 - Avoid transitive downgrade for .NET Framework targets (PR [#3279](https://github.com/dotnet/SqlClient/pull/3279)) + - [System.Text.Json](https://www.nuget.org/packages/System.Text.Json/6.0.11) 6.0.11 - Avoid transitive dependencies on older vulnerable versions for .NET Framework targets (PR [#3279](https://github.com/dotnet/SqlClient/pull/3279)) + ## [Stable release 5.1.6] - 2024-08-27 ### Fixed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 23335e3152..bf3bbd952f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,13 +7,14 @@ General contribution guidance is included in this document. Additional guidance ## Up for Grabs -The team marks the most straightforward issues as "up for grabs". This set of issues is the place to start if you are interested in contributing but new to the codebase. +The team marks the most straightforward issues as "up for grabs" and issues that are well suited to get you started as "Good first issue". This set of issues is the place to start if you are interested in contributing but new to the codebase. -- [dotnet/sqlclient - "up for grabs"](https://github.com/dotnet/sqlclient/labels/Up-for-Grabs) +- [dotnet/sqlclient - :raised_hands: Up-for-Grabs](https://github.com/dotnet/sqlclient/labels/%3Araised_hands%3A%20Up-for-Grabs) +- [dotnet/sqlclient - :sparkles: Good first issue](https://github.com/dotnet/sqlclient/labels/%3Asparkles%3A%20Good%20first%20issue) ## Contribution "Bar" -Project maintainers will merge changes that improve the product significantly and broadly and that align with the [Microsoft.Data.SqlClient roadmap](https://github.com/dotnet/sqlclient/blob/master/roadmap.md). +Project maintainers will merge changes that improve the product significantly and broadly and that align with the [Microsoft.Data.SqlClient roadmap](roadmap.md). Contributions must also satisfy the other published guidelines defined in this document. @@ -23,7 +24,7 @@ Please do: - **DO** report each issue as a new issue (but check first if it's already been reported) - **DO** respect Issue Templates and provide detailed information. It will make the process to reproduce the issue and provide a fix faster. - **DO** provide a minimal repro app demonstrating the problem in isolation will greatly speed up the process of identifying and fixing problems. -- **DO** follow our [coding style](coding-style.md) (C# code-specific) when working on a Pull Request. +- **DO** follow our [coding style](/policy/coding-style.md) (C# code-specific) when working on a Pull Request. - **DO** give priority to the current style of the project or file you're changing even if it diverges from the general guidelines. - **DO** consider cross-platform compatibility and supportability for all supported SQL and Azure Servers and client configurations. - **DO** include tests when adding new features. When fixing bugs, start with adding a test that highlights how the current behavior is broken. @@ -42,19 +43,21 @@ Please do not: ## Using Labels -We encourage adding appropriate labels to issues and pull requests to allow filtering and analysis effectively. The below variables are required for new Pull Requests if applicable: +As adding labels [is not possible](https://stackoverflow.com/questions/13829466/how-to-put-a-label-on-an-issue-in-github-if-you-are-not-a-contributor-owner/13829505#13829505) for contributors, please comment in the issue and pull request on what labels should be added. +The below variables are required for new Pull Requests if applicable: | Label | Description | | ----- | ----------- | -| [Public API](https://github.com/dotnet/SqlClient/labels/Public%20API) | Use this variable if a new Public API is added to the Pull Request. -| [Breaking Change](https://github.com/dotnet/SqlClient/labels/Breaking%20Change) | Use this variable if the Pull Request breaks an existing API. | -| [Backport to CoreFx](https://github.com/dotnet/SqlClient/labels/Backport%20to%20CoreFx) | Use this variable if the Issue/Pull Request needs to be backported to System.Data.SqlClient in [dotnet/corefx](https://github.com/dotnet/corefx) | -| [Managed SNI](https://github.com/dotnet/SqlClient/labels/Managed%20SNI) | Use this label if the issue/PR relates to issues in Managed SNI | -| [Tests](https://github.com/dotnet/SqlClient/labels/Tests) | Use this label for pull requests that add only tests to the repository. | +| [:new: Public API](https://github.com/dotnet/SqlClient/labels/%3Anew%3A%20Public%20API) | Use this variable if a new Public API is added to the Pull Request. +| [:hammer: Breaking Change](https://github.com/dotnet/SqlClient/labels/%3Ahammer%3A%20Breaking%20Change) | Use this variable if the Pull Request breaks an existing API. | +| [Area\Managed SNI](https://github.com/dotnet/SqlClient/labels/Area%5cManaged%20SNI) | Use this label if the issue/PR relates to issues in Managed SNI | +| [Area\Tests](https://github.com/dotnet/SqlClient/labels/Area%5cTests) | Use this label for pull requests that add only tests to the repository. | + +You can find all possible labels [here](https://github.com/dotnet/SqlClient/labels) ## Reporting security issues and security bugs -Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) [secure@microsoft.com](mailto:secure@microsoft.com). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Further information, including the MSRC PGP key, can be found in the [Security TechCenter](https://technet.microsoft.com/en-us/security/ff852094.aspx). +Security issues and bugs should be reported privately, via email, to the Microsoft Security Response Center (MSRC) [secure@microsoft.com](mailto:secure@microsoft.com). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Further information, including the MSRC PGP key, can be found in the [MSRC FAQ](https://www.microsoft.com/en-us/msrc/faqs-report-an-issue?rtc=1&oneroute=true). ## File Headers @@ -70,7 +73,7 @@ The following file header is used for Microsoft.Data.SqlClient. Please use it fo You must sign a [.NET Foundation Contribution License Agreement (CLA)](https://cla.dotnetfoundation.org) before your PR will be merged. This is a one-time requirement for projects in the .NET Foundation. You can read more about [Contribution License Agreements (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) on Wikipedia. -The agreement: [net-foundation-contribution-license-agreement.pdf](https://github.com/dotnet/home/blob/master/guidance/net-foundation-contribution-license-agreement.pdf) +The agreement: [contribution-license-agreement.pdf](https://cla.dotnetfoundation.org) You don't have to do this up-front. You can simply clone, fork, and submit your pull-request as usual. When your pull-request is created, it is classified by a CLA bot. If the change is trivial (for example, you just fixed a typo), then the PR is labelled with `cla-not-required`. Otherwise it's classified as `cla-required`. Once you signed a CLA, the current and all future pull-requests will be labelled as `cla-signed`. diff --git a/COPYRIGHT.md b/COPYRIGHT.md index f9013b465f..a1df5f1f9f 100644 --- a/COPYRIGHT.md +++ b/COPYRIGHT.md @@ -6,4 +6,4 @@ ## Binary License -- **Microsoft.Data.SqlClient** binary distributions (nuget packages) are licensed as MIT (identical to the [Microsoft.Data.SqlClient source license](https://github.com/dotnet/sqlclient/blob/master/LICENSE)). +- **Microsoft.Data.SqlClient** binary distributions (NuGet packages) are licensed as MIT (identical to the [Microsoft.Data.SqlClient source license](LICENSE)). diff --git a/README.md b/README.md index 224becd100..0397bae238 100644 --- a/README.md +++ b/README.md @@ -1,115 +1,119 @@ -[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://raw.githubusercontent.com/dotnet/sqlclient/master/LICENSE) -[![Nuget](https://img.shields.io/nuget/dt/Microsoft.Data.SqlClient?label=Nuget.org%20Downloads&style=flat-square&color=blue)](https://www.nuget.org/packages/Microsoft.Data.SqlClient) -[![Build status](https://sqlclientdrivers.visualstudio.com/public/_apis/build/status/ADO/CI-SqlClient)](https://sqlclientdrivers.visualstudio.com/public/_build/latest?definitionId=1879) - -# Microsoft SqlClient Data Provider for SQL Server - -Microsoft.Data.SqlClient is a .NET data provider for [Microsoft SQL Server]([url](https://aka.ms/sql)) and the [Azure SQL]([url](https://aka.ms/azure_sql)) family of databases. It grew from a union of the two System.Data.SqlClient components which live independently in .NET Framework and .NET Core. Going forward, support for new SQL Server and Azure SQL features will only be implemented in Microsoft.Data.SqlClient. - -## Supportability - -The Microsoft.Data.SqlClient package supports the following environments: - -- .NET Framework 4.6.2+ -- .NET 8.0 - -## Download - -The Microsoft.Data.SqlClient NuGet package is available on [NuGet.org](https://www.nuget.org/packages/Microsoft.Data.SqlClient/). - -## SNI Package References - -When targeting .NET Framework on Windows, a package reference to [Microsoft.Data.SqlClient.SNI](https://www.nuget.org/packages/Microsoft.Data.SqlClient.SNI/) loads native `Microsoft.Data.SqlClient.SNI..dll` libraries into the client's build directories. - -When targeting .NET on Windows, a package reference to [Microsoft.Data.SqlClient.SNI.runtime](https://www.nuget.org/packages/Microsoft.Data.SqlClient.SNI.runtime/) loads `arm64`, `x64` and `x86` native `Microsoft.Data.SqlClient.SNI.dll` libraries into subdirectories in the client's build directory. - -## Helpful Links - -| Topic | Link to File | -| :---- | :------------- | -| Coding Style | [coding-style.md](coding-style.md) | -| Guidelines for building the driver | [BUILDGUIDE.md](BUILDGUIDE.md) | -| Guidelines for Contributors | [CONTRIBUTING.md](CONTRIBUTING.md) | -| Changelog for all driver releases | [CHANGELOG.md](CHANGELOG.md) | -| Support Policy | [SUPPORT.md](SUPPORT.md) | -| Code of Conduct | [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) | -| Copyright Information | [COPYRIGHT.md](COPYRIGHT.md) | - -## Our Featured Contributors - -Special thanks to everyone who has contributed to the project. -We thank you for your continuous support in improving the SqlClient library! - -- Wraith ([@Wraith2](https://github.com/Wraith2)) -- Erik Ejlskov Jensen ([@ErikEJ](https://github.com/ErikEJ)) -- Simon Cropp ([@SimonCropp](https://github.com/SimonCropp)) -- Stefán Jökull Sigurðarson ([@stebet](https://github.com/stebet)) -- Shay Rojansky ([@roji](https://github.com/roji)) -- Stephen Toub ([@stephentoub](https://github.com/stephentoub)) -- Rasmus Melchior Jacobsen ([@rmja](https://github.com/rmja)) -- Phillip Haydon ([@phillip-haydon](https://github.com/phillip-haydon)) -- Robin Sue ([@Suchiman](https://github.com/Suchiman)) - -Up-to-date list of contributors: [Contributor Insights](https://github.com/dotnet/SqlClient/graphs/contributors) - -## Release Notes - -All preview and stable driver release notes are available under [release-notes](release-notes). - -## Porting from System.Data.SqlClient - -Refer to [porting-cheat-sheet.md](porting-cheat-sheet.md) for a safe porting experience from System.Data.SqlClient to Microsoft.Data.SqlClient and share your experience with us by enhancing this guide for future developers. - -## Still have questions? - -Check out our [FAQ](https://github.com/dotnet/SqlClient/wiki/Frequently-Asked-Questions). Still not answered? Create an [issue](https://github.com/dotnet/SqlClient/issues/new/choose) to ask a question. - - - -## Security - -Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). - -If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. - -## Reporting Security Issues - -**Please do not report security vulnerabilities through public GitHub issues.** - -Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). - -If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). - -You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). - -Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: - - * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) - * Full paths of source file(s) related to the manifestation of the issue - * The location of the affected source code (tag/branch/commit or direct URL) - * Any special configuration required to reproduce the issue - * Step-by-step instructions to reproduce the issue - * Proof-of-concept or exploit code (if possible) - * Impact of the issue, including how an attacker might exploit the issue - -This information will help us triage your report more quickly. - -If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. - -## Preferred Languages - -We prefer all communications to be in English. - -## Policy - -Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). - - - -## License - -The Microsoft.Data.SqlClient Driver for SQL Server is licensed under the MIT license. See the [LICENSE](https://github.com/dotnet/SqlClient/blob/master/LICENSE) file for more details. - -## Trademarks - -This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies. +[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE) +[![Nuget](https://img.shields.io/nuget/dt/Microsoft.Data.SqlClient?label=Nuget.org%20Downloads&style=flat-square&color=blue)](https://www.nuget.org/packages/Microsoft.Data.SqlClient) +[![Build status](https://sqlclientdrivers.visualstudio.com/public/_apis/build/status/ADO/CI-SqlClient)](https://sqlclientdrivers.visualstudio.com/public/_build/latest?definitionId=1879) + +# Microsoft SqlClient Data Provider for SQL Server + +Microsoft.Data.SqlClient is a .NET data provider for [Microsoft SQL Server]([url](https://aka.ms/sql)) and the [Azure SQL]([url](https://aka.ms/azure_sql)) family of databases. It grew from a union of the two System.Data.SqlClient components which live independently in .NET and .NET Framework. Going forward, support for new SQL Server and Azure SQL features will only be implemented in Microsoft.Data.SqlClient. + +## Supportability + +The Microsoft.Data.SqlClient package supports the following environments: + +- .NET Framework 4.6.2+ +- .NET 8.0+ + +## Download + +The Microsoft.Data.SqlClient NuGet package is available on [NuGet.org](https://www.nuget.org/packages/Microsoft.Data.SqlClient/). + +## SNI Package References + +When targeting .NET Framework on Windows, a package reference to [Microsoft.Data.SqlClient.SNI](https://www.nuget.org/packages/Microsoft.Data.SqlClient.SNI/) loads native `Microsoft.Data.SqlClient.SNI..dll` libraries into the client's build directories. + +When targeting .NET on Windows, a package reference to [Microsoft.Data.SqlClient.SNI.runtime](https://www.nuget.org/packages/Microsoft.Data.SqlClient.SNI.runtime/) loads `arm64`, `x64` and `x86` native `Microsoft.Data.SqlClient.SNI.dll` libraries into subdirectories in the client's build directory. + +## Helpful Links + +| Topic | Link to File | +| :---- | :------------- | +| Coding Style | [coding-style.md](/policy/coding-style.md) | +| Best Practices | [coding-best-practices.md](/policy/coding-best-practices.md) | +| Review Process | [review-process.md](/policy/review-process.md) | +| Guidelines for building the driver | [BUILDGUIDE.md](BUILDGUIDE.md) | +| Guidelines for Contributors | [CONTRIBUTING.md](CONTRIBUTING.md) | +| Changelog for all driver releases | [CHANGELOG.md](CHANGELOG.md) | +| Support Policy | [SUPPORT.md](SUPPORT.md) | +| Code of Conduct | [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) | +| Copyright Information | [COPYRIGHT.md](COPYRIGHT.md) | + +## Our Featured Contributors + +Special thanks to everyone who has contributed to the project. +We thank you for your continuous support in improving the SqlClient library! + +- Edward Neal ([@edwardneal](https://github.com/edwardneal)) +- Erik Ejlskov Jensen ([@ErikEJ](https://github.com/ErikEJ)) +- Michel Zehnder ([@MichelZ](https://github.com/MichelZ)) +- Shay Rojansky ([@roji](https://github.com/roji)) +- Phillip Haydon ([@phillip-haydon](https://github.com/phillip-haydon)) +- Rasmus Melchior Jacobsen ([@rmja](https://github.com/rmja)) +- Robin Sue ([@Suchiman](https://github.com/Suchiman)) +- Simon Cropp ([@SimonCropp](https://github.com/SimonCropp)) +- Stefán Jökull Sigurðarson ([@stebet](https://github.com/stebet)) +- Stephen Toub ([@stephentoub](https://github.com/stephentoub)) +- Wraith ([@Wraith2](https://github.com/Wraith2)) + +Up-to-date list of contributors: [Contributor Insights](https://github.com/dotnet/SqlClient/graphs/contributors) + +## Release Notes + +All preview and stable driver release notes are available under [release-notes](release-notes). + +## Porting from System.Data.SqlClient + +Refer to [porting-cheat-sheet.md](porting-cheat-sheet.md) for a safe porting experience from System.Data.SqlClient to Microsoft.Data.SqlClient and share your experience with us by enhancing this guide for future developers. + +## Still have questions? + +Check out our [FAQ](https://github.com/dotnet/SqlClient/wiki/Frequently-Asked-Questions). Still not answered? Create an [issue](https://github.com/dotnet/SqlClient/issues/new/choose) to ask a question. + + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://www.microsoft.com/en-us/msrc/definition-of-a-security-vulnerability), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + +- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) +- Full paths of source file(s) related to the manifestation of the issue +- The location of the affected source code (tag/branch/commit or direct URL) +- Any special configuration required to reproduce the issue +- Step-by-step instructions to reproduce the issue +- Proof-of-concept or exploit code (if possible) +- Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). + + + +## License + +The Microsoft.Data.SqlClient Driver for SQL Server is licensed under the MIT license. See the [LICENSE](LICENSE) file for more details. + +## Trademarks + +This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..a32cb34e57 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,38 @@ +# Security Policy + +## Supported Versions + +Microsoft.Data.SqlClient library follows the latest .NET Core support policy for all releases. + +[View the .NET Core Support Policy](https://dotnet.microsoft.com/platform/support/policy/dotnet-core) + +View GA released version LTS/Current status and support dates here: [SqlClient Support Policy on Microsoft Documentation](https://docs.microsoft.com/sql/connect/ado-net/sqlclient-driver-support-lifecycle) + +# Security +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include Microsoft, Azure, DotNet, AspNet, Xamarin, and our GitHub organizations. + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's Microsoft's definition of a security vulnerability, please report it to us as described below. + +## Reporting a Vulnerability + +Please do not report security vulnerabilities through public GitHub issues. + +Instead, please report them to the Microsoft Security Response Center (MSRC) at https://msrc.microsoft.com/create-report. + +If you prefer to submit without logging in, send email to secure@microsoft.com. If possible, encrypt your message with our PGP key; please download it from the Microsoft Security Response Center PGP Key page. + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at microsoft.com/msrc. + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + Full paths of source file(s) related to the manifestation of the issue + The location of the affected source code (tag/branch/commit or direct URL) + Any special configuration required to reproduce the issue + Step-by-step instructions to reproduce the issue + Proof-of-concept or exploit code (if possible) + Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our Microsoft Bug Bounty Program page for more details about our active programs. diff --git a/azurepipelines-coverage.yml b/azurepipelines-coverage.yml new file mode 100644 index 0000000000..f3434430bb --- /dev/null +++ b/azurepipelines-coverage.yml @@ -0,0 +1,16 @@ +# Azure DevOps code coverage settings: +# +# https://learn.microsoft.com/en-us/azure/devops/pipelines/test/codecoverage-for-pullrequests?view=azure-devops#configuring-coverage-settings +# +coverage: + # Code coverage status will be posted to pull requests based on targets + # defined below. + status: + # Off by default. When on, details about coverage for each file changed will + # be posted as a pull request comment. + comments: on + # Diff coverage is code coverage only for the lines changed in a pull + # request. + diff: + # Set this to a desired %. Default is 70%. + target: 70% diff --git a/build.proj b/build.proj index efa250d1a0..90f5f0fc37 100644 --- a/build.proj +++ b/build.proj @@ -1,4 +1,4 @@ - + @@ -22,7 +22,7 @@ net9.0 netfx netcore - netfx + netfx netcoreapp $(TF) $(TF) @@ -39,7 +39,7 @@ DebugType=portable;DebugSymbols=true;IncludeSymbols=true;SymbolPackageFormat=snupkg;PublishRepositoryUrl=true;RepositoryUrl=https://github.com/dotnet/sqlclient;RepositoryType=git;EmbedUnTrackedSources=true;Deterministic=true; - + $(NugetPackProperties);ContinuousIntegrationBuild=true; @@ -51,24 +51,32 @@ - + + + + + + + + - - - + + + + - + @@ -77,11 +85,11 @@ - - + + - - + + @@ -93,17 +101,19 @@ - - + + + - + - - + + + @@ -122,12 +132,12 @@ - + - - + + @@ -139,59 +149,205 @@ + + + + + + + + + - + - - + + - - + + - - + + + + + + + - - + + - - + + - - + + - - + + - - - - - - + + + + + + + + + + + + $(DotnetPath)dotnet test "@(UnitTestsProj)" + --no-build + -v n + -p:Configuration=$(Configuration) + -p:Target$(TFGroup)Version=$(TF) + -p:TestTargetOS=Windows$(TargetGroup) + --collect "Code coverage" + --results-directory $(ResultsDirectory) + --logger:"trx;LogFilePrefix=Unit-Windows$(TargetGroup)-$(TestSet)" + + $(TestCommand.Replace($([System.Environment]::NewLine), " ")) + + + + + + + + + + $(DotnetPath)dotnet test "@(UnitTestsProj)" + --no-build + -v n + -p:Configuration=$(Configuration) + -p:TargetNetCoreVersion=$(TF) + -p:TestTargetOS=Unixnetcoreapp + --collect "Code coverage" + --results-directory $(ResultsDirectory) + --logger:"trx;LogFilePrefix=Unit-Unixnetcoreapp-$(TestSet)" + + $(TestCommand.Replace($([System.Environment]::NewLine), " ")) + + + + + + + + + + + + + $(DotnetPath)dotnet test "@(FunctionalTestsProj)" + --no-build + -v n + -p:Configuration=$(Configuration) + -p:Target$(TFGroup)Version=$(TF) + -p:ReferenceType=$(ReferenceType) + -p:TestSet=$(TestSet) + -p:TestTargetOS=Windows$(TargetGroup) + --collect "Code coverage" + --results-directory $(ResultsDirectory) + --filter "category!=non$(TargetGroup)tests&category!=failing&category!=nonwindowstests" + --logger:"trx;LogFilePrefix=Functional-Windows$(TargetGroup)-$(TestSet)" + + $(TestCommand.Replace($([System.Environment]::NewLine), " ")) + + + + + + + + + + $(DotnetPath)dotnet test "@(FunctionalTestsProj)" + --no-build + -v n + -p:Configuration=$(Configuration) + -p:TargetNetCoreVersion=$(TF) + -p:ReferenceType=$(ReferenceType) + -p:TestSet=$(TestSet) + -p:TestTargetOS=Unixnetcoreapp + --collect "Code coverage" + --results-directory $(ResultsDirectory) + --filter "category!=nonnetcoreapptests&category!=failing&category!=nonlinuxtests&category!=nonuaptests" + --logger:"trx;LogFilePrefix=Functional-Unixnetcoreapp-$(TestSet)" + + $(TestCommand.Replace($([System.Environment]::NewLine), " ")) + + + - - - - - + + + + + + + + $(DotnetPath)dotnet test "@(ManualTestsProj)" + --no-build + -v n + -p:Configuration=$(Configuration) + -p:Target$(TFGroup)Version=$(TF) + -p:ReferenceType=$(ReferenceType) + -p:TestSet=$(TestSet) + -p:TestTargetOS=Windows$(TargetGroup) + --collect "Code coverage" + --results-directory $(ResultsDirectory) + --filter "category!=non$(TargetGroup)tests&category!=failing&category!=nonwindowstests" + --logger:"trx;LogFilePrefix=Manual-Windows$(TargetGroup)-$(TestSet)" + + $(TestCommand.Replace($([System.Environment]::NewLine), " ")) + + + + + + + + $(DotnetPath)dotnet test "@(ManualTestsProj)" + --no-build + -v n + -p:Configuration=$(Configuration) + -p:TargetNetCoreVersion=$(TF) + -p:ReferenceType=$(ReferenceType) + -p:TestSet=$(TestSet) + -p:TestTargetOS=Unixnetcoreapp + --collect "Code coverage" + --results-directory $(ResultsDirectory) + --filter "category!=nonnetcoreapptests&category!=failing&category!=nonlinuxtests&category!=nonuaptests" + --logger:"trx;LogFilePrefix=Manual-Unixnetcoreapp-$(TestSet)" + + $(TestCommand.Replace($([System.Environment]::NewLine), " ")) + + + + + + @@ -200,24 +356,57 @@ + + + + + + + + + + $(CI);TestTargetOS=$(TestOS)netfx;Platform=AnyCPU;$(ProjectProperties);$(NugetPackProperties) + + + + + + $(CI);TestTargetOS=$(TestOS)netcoreapp;$(ProjectProperties);Platform=AnyCPU;OSGroup=Unix; + + + + + + $(CI);TestTargetOS=$(TestOS)netcoreapp;$(ProjectProperties);Platform=AnyCPU;OSGroup=Windows_NT + + + + + + $(CI);TestTargetOS=$(TestOS)netcoreapp;$(ProjectProperties);Platform=AnyCPU;OSGroup=AnyOS; + + + + + - - + + - - + + - - + + - - + + diff --git a/contributing-workflow.md b/contributing-workflow.md index 362bb60c87..a684849320 100644 --- a/contributing-workflow.md +++ b/contributing-workflow.md @@ -10,22 +10,23 @@ We use and recommend the following workflow: - You can skip this step for trivial changes. - Reuse an existing issue on the topic, if there is one. - Get agreement from the team and the community that your proposed change is a good one. - - If your requested change needs a new public API, add a Label "**Public API**" to your issue. - - If your requested change address performance improvement in the driver, add a Label "**Performance**" to your issue. - - For any other improvements in the driver, add a Label "**Enhancement**" to your issue. + - Suggest [Labels](CONTRIBUTING.md#using-labels) to add for your issue. - Clearly state that you are going to take on implementing it, if that's the case. You can request that the issue be assigned to you. Note: The issue filer and the implementer don't have to be the same person. 2. Create a personal fork of the repository on GitHub (if you don't already have one). -3. Create a branch off of master (`git checkout -b mybranch`). +3. Create a branch off of main (`git checkout -b mybranch`). - Name the branch so that it clearly communicates your intentions, such as issue-123 or githubhandle-issue. - Branches are useful since they isolate your changes from incoming changes from upstream. They also enable you to create multiple PRs from the same fork. 4. Make and commit your changes. - - Please follow our [Commit Messages](contributing.md#commit-messages) guidance. + - Please follow our guidance [here](CONTRIBUTING.md#dos-and-donts). 5. Add new tests corresponding to your change, if nor present and applicable. 6. Build the repository with your changes. - Make sure that the builds are clean. - Make sure that the tests are all passing, including your new tests. -7. Create a pull request (PR) against the upstream repository's **master** branch. +7. Create a pull request (PR) against the upstream repository's **main** branch. - Push your changes to your fork on GitHub (if you haven't already). + - Create a PR to the **main** branch + - Describe the changes that you have done in the PR + - Suggest [Labels](CONTRIBUTING.md#using-labels) to add to the PR in the description > Note: It is OK for your PR to include a large number of commits. Once your change is accepted, you will be asked to squash your commits into one or some appropriately small number of commits before your PR is merged. @@ -33,7 +34,7 @@ We use and recommend the following workflow: ## PR - CI Process -The [SqlClient Continuous Integration](https://dev.azure.com/sqlclientdrivers-ci/sqlclient/) (CI) system will automatically perform the required builds and run tests (including the ones you are expected to run) for PRs. Builds and test runs must be clean. +The [SqlClient Continuous Integration](https://dev.azure.com/sqlclientdrivers/public/_build) (CI) system will automatically perform the required builds and run tests (including the ones you are expected to run) for PRs. Builds and test runs must be clean. If the CI build fails for any reason, the PR issue will be updated with a link that can be used to determine the cause of the failure. @@ -50,64 +51,3 @@ Use ["Squash and Merge"](https://github.com/blog/2141-squash-your-commits) by de Do so, even if the PR contains only one commit. It creates a simpler history than "Create a Merge Commit". Reasons that PR authors may request "Merge and Commit" may include (but are not limited to): - The change is easier to understand as a series of focused commits. Each commit in the series must be buildable so as not to break `git bisect`. - Contributor is using an e-mail address other than the primary GitHub address and wants that preserved in the history. Contributor must be willing to squash the commits manually before acceptance. - -## PR Verification and Validation - -For testing PR changes and ensure they work fine, we maintain a public feed that lists NuGet Packages built with merged commits. - -**Link to Public Feed:** [Microsoft.Data.SqlClient.Commits](https://pkgs.dev.azure.com/sqlclientdrivers-ci/sqlclient/_packaging/Microsoft.Data.SqlClient.Commits/nuget/v3/index.json) - -**Add this feed to NuGet Sources** - -```xml - - - - - - - -``` -OR -```cmd -nuget.exe sources Add -Name "Microsoft.Data.SqlClient.Commits" -Source "https://pkgs.dev.azure.com/sqlclientdrivers-ci/sqlclient/_packaging/Microsoft.Data.SqlClient.Commits/nuget/v3/index.json" -``` - -### Pushing to Public Feed - -Maintainers of [dotnet/sqlclient](https://github.com/dotnet/SqlClient) manually push NuGet Packages to this feed after the PR is merged to the `master` branch and a commit ID has been generated on `master`. - -Not every commit is built and published on the public CI feed, but only the commits that make considerable difference to the driver will be uploaded in this feed. - -The label **Push to Commit Feed** will be added to the Pull Request if it is pushed to the feed. - -### Downloading Packages - -The package naming conventions follow SemVer 2.0.0 and also provide changeset information to ensure the right PR changes are being downloaded for testing: - -Package names will be like: `Microsoft.Data.SqlClient.1.1.0-build.19250.1-c21aa7c.nupkg` - -Breaking down: -- `1.1.0-build` - Identifier for currently active driver version in Build. -- `19250.1` - Unique identifier to keep latest PRs on top of the feed. -- `c21aa7c` - Short Commit Id to identify merged commit in `master`. - -> Note: This public feed is only for testing and validating new PR changes. Packages from feed will be eventually removed when the maximum NuGet Package limit of **50** is reached. We do not recommend using packages from this feed in client applications. - -## Nightly builds - -We also maintain NuGet feed for Nightly builds that are generated when new changes are merged in the repository. To access Nightly builds, add below configuration to package sources: - -```xml - - - - - - - -``` -OR -```cmd -nuget.exe sources Add -Name "Microsoft.Data.SqlClient.dev" -Source "https://pkgs.dev.azure.com/sqlclientdrivers-ci/sqlclient/_packaging/Microsoft.Data.SqlClient.dev/nuget/v3/index.json" -``` diff --git a/doc/samples/SqlConnectionStringBuilder.cs b/doc/samples/SqlConnectionStringBuilder.cs index f1c3252880..d1d22254f1 100644 --- a/doc/samples/SqlConnectionStringBuilder.cs +++ b/doc/samples/SqlConnectionStringBuilder.cs @@ -21,12 +21,12 @@ static void Main() // connection string, and you can retrieve and // modify any of the elements. builder.ConnectionString = "server=(local);user id=ab;" + - "password= a!Pass113;initial catalog=AdventureWorks"; + "password=********;initial catalog=AdventureWorks"; // Now that the connection string has been parsed, // you can work with individual items. Console.WriteLine(builder.Password); - builder.Password = "new@1Password"; + builder.Password = "********"; // You can refer to connection keys using strings, // as well. When you use this technique (the default diff --git a/doc/samples/SqlConnectionStringBuilder3.cs b/doc/samples/SqlConnectionStringBuilder3.cs index 3e0c253039..fc691489fe 100644 --- a/doc/samples/SqlConnectionStringBuilder3.cs +++ b/doc/samples/SqlConnectionStringBuilder3.cs @@ -10,7 +10,7 @@ static void Main() try { string connectString = - "Server=(local);Database=AdventureWorks;UID=ab;Pwd= a!Pass@@"; + "Server=(local);Database=AdventureWorks;UID=ab;Pwd=********"; Console.WriteLine("Original: " + connectString); SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectString); diff --git a/doc/samples/SqlConnectionStringBuilder_IntegratedSecurity.cs b/doc/samples/SqlConnectionStringBuilder_IntegratedSecurity.cs index 693298dd35..b2e6773559 100644 --- a/doc/samples/SqlConnectionStringBuilder_IntegratedSecurity.cs +++ b/doc/samples/SqlConnectionStringBuilder_IntegratedSecurity.cs @@ -10,7 +10,7 @@ static void Main() try { string connectString = - "Data Source=(local);User ID=ab;Password=MyPassword;" + + "Data Source=(local);User ID=ab;Password=********;" + "Initial Catalog=AdventureWorks"; SqlConnectionStringBuilder builder = diff --git a/doc/samples/SqlConnectionStringBuilder_Remove.cs b/doc/samples/SqlConnectionStringBuilder_Remove.cs index 7de94386a1..835b8906c3 100644 --- a/doc/samples/SqlConnectionStringBuilder_Remove.cs +++ b/doc/samples/SqlConnectionStringBuilder_Remove.cs @@ -10,7 +10,7 @@ static void Main() try { string connectString = - "Data Source=(local);User ID=ab;Password= a1Pass@@11;" + + "Data Source=(local);User ID=ab;Password=********;" + "Initial Catalog=AdventureWorks"; SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectString); diff --git a/doc/samples/SqlConnection_AccessTokenCallback.cs b/doc/samples/SqlConnection_AccessTokenCallback.cs index 3477e8a4de..bcd10cfe56 100644 --- a/doc/samples/SqlConnection_AccessTokenCallback.cs +++ b/doc/samples/SqlConnection_AccessTokenCallback.cs @@ -1,5 +1,5 @@ -using System; // +using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; @@ -17,10 +17,10 @@ static void Main() const string defaultScopeSuffix = "/.default"; - // Reuse credential objects to take advantage of underlying token caches + // Reuse credential objects to take advantage of underlying token caches. private static ConcurrentDictionary credentials = new ConcurrentDictionary(); - // Use a shared callback function for connections that should be in the same connection pool + // Use a shared callback function for connections that should be in the same connection pool. private static Func> myAccessTokenCallback = async (authParams, cancellationToken) => { @@ -31,7 +31,7 @@ static void Main() DefaultAzureCredentialOptions options = new DefaultAzureCredentialOptions(); options.ManagedIdentityClientId = authParams.UserId; - // Reuse the same credential object if we are using the same MI Client Id + // Reuse the same credential object if we are using the same MI Client Id. AccessToken token = await credentials.GetOrAdd(authParams.UserId, new DefaultAzureCredential(options)).GetTokenAsync( new TokenRequestContext(new string[] { scope }), cancellationToken); diff --git a/doc/snippets/Microsoft.Data.Sql/SqlDataSourceEnumerator.xml b/doc/snippets/Microsoft.Data.Sql/SqlDataSourceEnumerator.xml index 57357e96e9..eaa7661f1c 100644 --- a/doc/snippets/Microsoft.Data.Sql/SqlDataSourceEnumerator.xml +++ b/doc/snippets/Microsoft.Data.Sql/SqlDataSourceEnumerator.xml @@ -18,47 +18,25 @@ A containing information about the visible SQL Server instances. - - - The table returned by this method contains the following columns, all of which contain strings: - - - - - Column - Description - - - ServerName - Name of the server. - - - InstanceName - Name of the server instance. Blank if the server is running as the default instance. - - - IsClustered - Indicates whether the server is part of a cluster. - - - Version - - Version of the server: - - 10.0.xx for SQL Server 2008 - 10.50.x for SQL Server 2008 R2 - 11.0.xx for SQL Server 2012 - 12.0.xx for SQL Server 2014 - 13.0.xx for SQL Server 2016 - 14.0.xx for SQL Server 2017 - - - - - - Due to the nature of the mechanism used by to locate data sources on a network, the method will not always return a complete list of the available servers, and the list might not be the same on every call. If you plan to use this function to let users select a server from a list, make sure that you always also supply an option to type in a name that is not in the list, in case the server enumeration does not return all the available servers. In addition, this method may take a significant amount of time to execute, so be careful about calling it when performance is critical. - - + + +
  • 10.0.xx for SQL Server 2008
  • 10.50.x for SQL Server 2008 R2
  • 11.0.xx for SQL Server 2012
  • 12.0.xx for SQL Server 2014
  • 13.0.xx for SQL Server 2016
  • 14.0.xx for SQL Server 2017
  • | + +> [!NOTE] +> Due to the nature of the mechanism used by to locate data sources on a network, the method will not always return a complete list of the available servers, and the list might not be the same on every call. If you plan to use this function to let users select a server from a list, make sure that you always also supply an option to type in a name that is not in the list, in case the server enumeration does not return all the available servers. In addition, this method may take a significant amount of time to execute, so be careful about calling it when performance is critical. + + ]]> +
    diff --git a/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml index a6444f700a..a940011eb3 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml @@ -180,14 +180,14 @@ The supported authentication modes with are: - Active Directory Password - Active Directory Integrated - Active Directory Interactive - Active Directory Service Principal - Active Directory Device Code Flow - Active Directory Managed Identity - Active Directory MSI - Active Directory Default + Active Directory Password + Active Directory Integrated + Active Directory Interactive + Active Directory Service Principal + Active Directory Device Code Flow + Active Directory Managed Identity + Active Directory MSI + Active Directory Default diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml index 6811f5735e..40f34cba48 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlBatch.xml @@ -51,7 +51,7 @@ - Initializes a new . + Initializes a new . The following example creates a and a , then adds multiple objects to the batch. It then executes the batch, creating a . The example reads through the results of the batch commands, writing them to the console. Finally, the example closes the and then the as the using blocks fall out of scope. @@ -103,7 +103,7 @@ - Initializes a new . + Initializes a new . A that represents the connection to an instance of SQL Server. @@ -176,7 +176,7 @@ - The list of commands contained in the batch in a . + The list of contained in the batch in a . @@ -309,10 +309,14 @@ A object. - When the batch returns multiple result sets from different commands, can be used to advance the reader to the next result set. - - This method benefits from , and all the expected exceptions of that method also apply here. - + + can be used to advance the reader to the next result set. + +> [!NOTE] +> This method benefits from , and all the expected exceptions of that method also apply here. + ]]> + An error occurred while executing the batch. @@ -329,15 +333,16 @@ A task representing the asynchronous operation. - - For more information about asynchronous programming, see Asynchronous Programming. - - - This method stores in the task it returns all non-usage exceptions that the method's synchronous counterpart can throw. If an exception is stored into the returned task, that exception will be thrown when the task is awaited. Usage exceptions, such as , are still thrown synchronously. For the stored exceptions, see the exceptions thrown by . - - - This method benefits from , and all the expected exceptions of that method also apply here. - + + , are still thrown synchronously. For the stored exceptions, see the exceptions thrown by . + +> [!NOTE] +> This method benefits from , and all the expected exceptions of that method also apply here. + ]]> + The cancellation token was canceled. This exception is stored into the returned task. @@ -349,10 +354,14 @@ The total number of rows affected across all the batch commands. - You can use to perform catalog operations (for example, querying the structure of a database or creating database objects such as tables), or to change the data in a database by executing UPDATE, INSERT, or DELETE statements. Although does not return any rows, any output parameters or return values mapped to parameters are populated with data. For UPDATE, INSERT, and DELETE statements, the return value is the total number of rows affected by the batch. If no UPDATE, INSERT, or DELETE statements are included in the batch, the return value is -1. - - This method benefits from , and all the expected exceptions of that method also apply here. - + + to perform catalog operations (for example, querying the structure of a database or creating database objects such as tables), or to change the data in a database by executing UPDATE, INSERT, or DELETE statements. Although does not return any rows, any output parameters or return values mapped to parameters are populated with data. For UPDATE, INSERT, and DELETE statements, the return value is the total number of rows affected by the batch. If no UPDATE, INSERT, or DELETE statements are included in the batch, the return value is -1. + +> [!NOTE] +> This method benefits from , and all the expected exceptions of that method also apply here. + ]]> + @@ -364,15 +373,16 @@ A task representing the asynchronous operation. - - For more information about asynchronous programming, see Asynchronous Programming. - - - If an exception is stored into the returned task, that exception will be thrown when the task is awaited. Usage exceptions, such as , are still thrown synchronously. - - - This method benefits from , and all the expected exceptions of that method also apply here. - + + , are still thrown synchronously. + +> [!NOTE] +> This method benefits from , and all the expected exceptions of that method also apply here. + ]]> + An error occurred while executing the batch. Overview of the SqlClient driver @@ -396,7 +406,7 @@ The first column of the first row in the first result set. - This method benefits from , and all the expected exceptions of that method also apply here. If an exception is stored into the returned task, that exception will be thrown when the task is awaited. Usage exceptions, such as , are still thrown synchronously. + This method benefits from , and all the expected exceptions of that method also apply here. If an exception is stored into the returned task, that exception will be thrown when the task is awaited. Usage exceptions, such as , are still thrown synchronously. An error occurred while executing the batch. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlBatchCommand.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlBatchCommand.xml index 2fe3166a6c..8f0be84d25 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlBatchCommand.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlBatchCommand.xml @@ -7,7 +7,7 @@ - Initializes a new . + Initializes a new . The following example creates a and a SqlBatch, then adds multiple objects to the batch. It then executes the batch, creating a . The example reads through the results of the batch commands, writing them to the console. Finally, the example closes the and then the as the using blocks fall out of scope. @@ -62,7 +62,7 @@ - Initializes a new . + Initializes a new . The text of the . diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopy.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopy.xml index 082e28aee9..461ece2b2a 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopy.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopy.xml @@ -5,99 +5,21 @@ Lets you efficiently bulk load a SQL Server table with data from another source. - Microsoft SQL Server includes a popular command-prompt utility named bcp for moving data from one table to another, whether on a single server or between servers. The class lets you write managed code solutions that provide similar functionality. There are other ways to load data into a SQL Server table (INSERT statements, for example), but offers a significant performance advantage over them. The class can be used to write data only to SQL Server tables. However, the data source is not limited to SQL Server; any data source can be used, as long as the data can be loaded to a instance or read with a instance. will fail when bulk loading a column of type into a SQL Server column whose type is one of the date/time types added in SQL Server 2008. + Microsoft SQL Server includes a popular command-prompt utility named **bcp** for moving data from one table to another, whether on a single server or between servers. The class lets you write managed code solutions that provide similar functionality. There are other ways to load data into a SQL Server table (INSERT statements, for example), but offers a significant performance advantage over them. The class can be used to write data only to SQL Server tables. However, the data source is not limited to SQL Server; any data source can be used, as long as the data can be loaded to a instance or read with a instance. will fail when bulk loading a column of type into a SQL Server column whose type is one of the date/time types added in SQL Server 2008. - The following console application demonstrates how to load data using the class. In this example, a is used to copy data from the Production.Product table in the SQL Server AdventureWorks database to a similar table in the same database. - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, ProductNumber FROM Production.Product;", - sourceConnection); - SqlDataReader reader = commandSourceData.ExecuteReader(); - - // Open the destination connection. In the real world you would - // not use SqlBulkCopy to move data from one table to the other - // in the same database. This is for demonstration purposes only. - using (SqlConnection destinationConnection = new SqlConnection(connectionString)) - { - destinationConnection.Open(); - - // Set up the bulk copy object. - // Note that the column positions in the source - // data reader match the column positions in - // the destination table so there is no need to - // map columns. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoMatchingColumns"; - - try - { - // Write from the source to the destination. - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + class. In this example, a is used to copy data from the **Production.Product** table in the SQL Server **AdventureWorks** database to a similar table in the same database. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[BulkCopy.Single#1](~/../sqlclient/doc/samples/SqlBulkCopy_Single.cs#1)] + +]]> @@ -108,104 +30,25 @@ Initializes a new instance of the class using the specified open instance of . - Because the connection is already open when the instance is initialized, the connection remains open after the instance is closed. If the connection argument is null, an is thrown. + Because the connection is already open when the instance is initialized, the connection remains open after the instance is closed. If the `connection` argument is null, an is thrown. - - The following console application demonstrates how to bulk load data using a connection that is already open. In this example, a is used to copy data from the Production.Product table in the SQL Server AdventureWorks database to a similar table in the same database. This example is for demonstration purposes only. You would not use SqlBulkCopy to move data from one table to another in the same database in a production application. Note that the source data does not have to be located on SQL Server; you can use any data source that can be read to an or loaded to a . - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, ProductNumber FROM Production.Product;", - sourceConnection); - SqlDataReader reader = commandSourceData.ExecuteReader(); - - // Open the destination connection. In the real world you would - // not use SqlBulkCopy to move data from one table to the other - // in the same database. This is for demonstration purposes only. - using (SqlConnection destinationConnection = new SqlConnection(connectionString)) - { - destinationConnection.Open(); - - // Set up the bulk copy object. - // Note that the column positions in the source - // data reader match the column positions in - // the destination table so there is no need to - // map columns. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoMatchingColumns"; - - try - { - // Write from the source to the destination. - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + +is used to copy data from the **Production.Product** table in the SQL Server **AdventureWorks** database to a similar table in the same database. This example is for demonstration +purposes only. You would not use `SqlBulkCopy` to move data from one table to another in the same database in a production application. +Note that the source data does not have to be located on SQL Server; you can use any data source that can be read to an or loaded to a +. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[BulkCopy.Single#1](~/../sqlclient/doc/samples/SqlBulkCopy_Single.cs#1)] + +]]> @@ -247,94 +90,24 @@ Initializes and opens a new instance of based on the supplied . The constructor uses the to initialize a new instance of the class. - The connection is automatically closed at the end of the bulk copy operation. If is , an is thrown. If is an empty string, an is thrown. + The connection is automatically closed at the end of the bulk copy operation. If `connectionString` is null, an is thrown. If `connectionString` is an empty string, an is thrown. - - The following console application demonstrates how to bulk load data by using a connection specified as a string. The connection is automatically closed when the instance is closed. In this example, the source data is first read from a SQL Server table to a instance. The source data does not have to be located on SQL Server; you can use any data source that can be read to an or loaded to a . - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, ProductNumber FROM Production.Product;", - sourceConnection); - SqlDataReader reader = commandSourceData.ExecuteReader(); - - // Set up the bulk copy object using a connection string. - // In the real world you would not use SqlBulkCopy to move - // data from one table to the other in the same database. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoMatchingColumns"; - - try - { - // Write from the source to the destination. - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + instance is closed. +In this example, the source data is first read from a SQL Server table to a instance. +The source data does not have to be located on SQL Server; you can use any data source that can be read to an or loaded to a . + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ConnectionString#1](~/../sqlclient/doc/samples/SqlBulkCopy_ConnectionString.cs#1)] + +]]> If is an empty string, an is thrown. @@ -354,92 +127,25 @@ You can obtain detailed information about all the bulk copy options in the topic.
    - - The following console application demonstrates how to perform a bulk load by using a connection specified as a string. An option is set to use the value in the identity column of the source table when you load the destination table. In this example, the source data is first read from a SQL Server table to a instance. The source table and destination table each include an Identity column. By default, a new value for the Identity column is generated in the destination table for each row added. In this example, an option is set when the connection is opened that forces the bulk load process to use the Identity values from the source table instead. To see how the option changes the way the bulk load works, run the sample with the dbo.BulkCopyDemoMatchingColumns table empty. All rows load from the source. Then run the sample again without emptying the table. An exception is thrown and the code writes a message to the console notifying you that rows weren't added because of primary key constraint violations. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, ProductNumber FROM Production.Product;", - sourceConnection); - SqlDataReader reader = commandSourceData.ExecuteReader(); - - // Create the SqlBulkCopy object using a connection string - // and the KeepIdentity option. - // In the real world you would not use SqlBulkCopy to move - // data from one table to the other in the same database. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString, SqlBulkCopyOptions.KeepIdentity)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoMatchingColumns"; - - try - { - // Write from the source to the destination. - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + instance. +The source table and destination table each include an Identity column. By default, a new value for the **Identity** column is generated in the destination table for each row added. +In this example, an option is set when the connection is opened that forces the bulk load process to use the **Identity** values from the source table instead. +To see how the option changes the way the bulk load works, run the sample with the **dbo.BulkCopyDemoMatchingColumns** table empty. All rows load from the source. +Then run the sample again without emptying the table. An exception is thrown and the code writes a message to the console notifying you that rows weren't +added because of primary key constraint violations. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.KeepIdentity#1](~/../sqlclient/doc/samples/SqlBulkCopy_KeepIdentity.cs#1)] + +]]> @@ -454,102 +160,30 @@ A batch is complete when rows have been processed or there are no more rows to send to the destination data source. - Zero (the default) indicates that each operation is a single batch. + Zero (the default) indicates that each operation is a single batch. If the instance has been declared without the option in effect, rows are sent to the server rows at a time, but no transaction-related action is taken. If is in effect, each batch of rows is inserted as a separate transaction. - The property can be set at any time. If a bulk copy is already in progress, the current batch is sized according to the previous batch size. Subsequent batches use the new size. If the is initially zero and changed while a operation is already in progress, that operation loads the data as a single batch. Any subsequent operations on the same instance use the new . + The property can be set at any time. If a bulk copy is already in progress, the current batch is sized according to the previous batch size. Subsequent batches use the new size. If the is initially zero and changed while a operation is already in progress, that operation loads the data as a single batch. Any subsequent operations on the same instance use the new . - - The following console application demonstrates how to bulk load data in batches of 50 rows. For an example illustrating how works with a transaction, see Transaction and Bulk Copy Operations. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, ProductNumber FROM Production.Product;", - sourceConnection); - SqlDataReader reader = commandSourceData.ExecuteReader(); - - // Create the SqlBulkCopy object using a connection string. - // In the real world you would not use SqlBulkCopy to move - // data from one table to the other in the same database. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoMatchingColumns"; - - // Set the BatchSize. - bulkCopy.BatchSize = 50; - - try - { - // Write from the source to the destination. - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + +works with a transaction, see [Transaction and Bulk Copy Operations](https://learn.microsoft.com/sql/connect/ado-net/sql/transaction-bulk-copy-operations). + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, +it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.BatchSize#1](~/../sqlclient/doc/samples/SqlBulkCopy_BatchSize.cs#1)] + +]]> @@ -560,99 +194,23 @@ The integer value of the property. The default is 30 seconds. A value of 0 indicates no limit; the bulk copy will wait indefinitely. - If the operation does time out, the transaction is not committed and all copied rows are removed from the destination table. + If the operation does time out, the transaction is not committed and all copied rows are removed from the destination table. - - The following console application demonstrates how to modify the time-out to 60 seconds when bulk loading data. In this example, the source data is first read from a SQL Server table to a instance. The source data does not have to be located on SQL Server; you can use any data source that can be read to an or loaded to a . - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = - new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, ProductNumber FROM Production.Product;", - sourceConnection); - SqlDataReader reader = commandSourceData.ExecuteReader(); - - // Create the SqlBulkCopy object using a connection string. - // In the real world you would not use SqlBulkCopy to move - // data from one table to the other in the same database. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoMatchingColumns"; - - // Set the timeout. - bulkCopy.BulkCopyTimeout = 60; - - try - { - // Write from the source to the destination. - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + instance. +The source data does not have to be located on SQL Server; you can use any data source that can be read to an or loaded to a . + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.Timeout#1](~/../sqlclient/doc/samples/SqlBulkCopy_Timeout.cs#1)] + +]]> @@ -660,171 +218,21 @@ Closes the instance. - After you call on the object, no other operation will succeed. Calls to the method will throw an . Calling the method from the event causes an to be thrown. Note that open instances are closed implicitly at the end of a using block. + After you call on the object, no other operation will succeed. Calls to the method will throw an . Calling the method from the event causes an to be thrown. Note that open instances are closed implicitly at the end of a using block. - - The following example uses the same instance to add sales orders and their associated details to two destination tables. Because the AdventureWorks sales order tables are large, the sample reads only orders placed by a certain account number and bulk copies those orders and details to the destination tables. The method is used only after both bulk copy operations are complete. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a connection to the AdventureWorks database. - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - // Empty the destination tables. - SqlCommand deleteHeader = new SqlCommand( - "DELETE FROM dbo.BulkCopyDemoOrderHeader;", - connection); - deleteHeader.ExecuteNonQuery(); - SqlCommand deleteDetail = new SqlCommand( - "DELETE FROM dbo.BulkCopyDemoOrderDetail;", - connection); - deleteDetail.ExecuteNonQuery(); - - // Perform an initial count on the destination - // table with matching columns. - SqlCommand countRowHeader = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderHeader;", - connection); - long countStartHeader = System.Convert.ToInt32( - countRowHeader.ExecuteScalar()); - Console.WriteLine( - "Starting row count for Header table = {0}", - countStartHeader); - - // Perform an initial count on the destination - // table with different column positions. - SqlCommand countRowDetail = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderDetail;", - connection); - long countStartDetail = System.Convert.ToInt32( - countRowDetail.ExecuteScalar()); - Console.WriteLine( - "Starting row count for Detail table = {0}", - countStartDetail); - - // Get data from the source table as a SqlDataReader. - // The Sales.SalesOrderHeader and Sales.SalesOrderDetail - // tables are quite large and could easily cause a timeout - // if all data from the tables is added to the destination. - // To keep the example simple and quick, a parameter is - // used to select only orders for a particular account - // as the source for the bulk insert. - SqlCommand headerData = new SqlCommand( - "SELECT [SalesOrderID], [OrderDate], " + - "[AccountNumber] FROM [Sales].[SalesOrderHeader] " + - "WHERE [AccountNumber] = @accountNumber;", - connection); - SqlParameter parameterAccount = new SqlParameter(); - parameterAccount.ParameterName = "@accountNumber"; - parameterAccount.SqlDbType = SqlDbType.NVarChar; - parameterAccount.Direction = ParameterDirection.Input; - parameterAccount.Value = "10-4020-000034"; - headerData.Parameters.Add(parameterAccount); - SqlDataReader readerHeader = headerData.ExecuteReader(); - - // Get the Detail data in a separate connection. - using (SqlConnection connection2 = new SqlConnection(connectionString)) - { - connection2.Open(); - SqlCommand sourceDetailData = new SqlCommand( - "SELECT [Sales].[SalesOrderDetail].[SalesOrderID], [SalesOrderDetailID], " + - "[OrderQty], [ProductID], [UnitPrice] FROM [Sales].[SalesOrderDetail] " + - "INNER JOIN [Sales].[SalesOrderHeader] ON [Sales].[SalesOrderDetail]." + - "[SalesOrderID] = [Sales].[SalesOrderHeader].[SalesOrderID] " + - "WHERE [AccountNumber] = @accountNumber;", - connection2); - - SqlParameter accountDetail = new SqlParameter(); - accountDetail.ParameterName = "@accountNumber"; - accountDetail.SqlDbType = SqlDbType.NVarChar; - accountDetail.Direction = ParameterDirection.Input; - accountDetail.Value = "10-4020-000034"; - sourceDetailData.Parameters.Add(accountDetail); - SqlDataReader readerDetail = sourceDetailData.ExecuteReader(); - - // Create the SqlBulkCopy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoOrderHeader"; - - // Write readerHeader to the destination. - try - { - bulkCopy.WriteToServer(readerHeader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - readerHeader.Close(); - } - - // Set up a different destination and - // map columns. - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoOrderDetail"; - - // Write readerDetail to the destination. - try - { - bulkCopy.WriteToServer(readerDetail); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - readerDetail.Close(); - } - } - - // Perform a final count on the destination - // tables to see how many rows were added. - long countEndHeader = System.Convert.ToInt32( - countRowHeader.ExecuteScalar()); - Console.WriteLine("{0} rows were added to the Header table.", - countEndHeader - countStartHeader); - - long countEndDetail = System.Convert.ToInt32( - countRowDetail.ExecuteScalar()); - Console.WriteLine("{0} rows were added to the Detail table.", - countEndDetail - countStartDetail); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - } - - private static string GetConnectionString() - { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + instance to add sales orders and their associated details to two destination tables. Because the **AdventureWorks** sales order tables are large, the sample reads only orders placed by a certain account number and bulk copies those orders and details to the destination tables. The method is used only after both bulk copy operations are complete. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.OrdersDetails#1](~/../sqlclient/doc/samples/SqlBulkCopy_OrdersDetails.cs#1)] + +]]> @@ -835,12 +243,17 @@ if a object can stream data from an object; otherwise, false. The default is . - - When is , reads from an object using , optimizing memory usage by using the streaming capabilities. Streaming is only applicable to max data types (i.e. VARBINARY(MAX), VARCHAR(MAX), NVARCHAR(MAX), and XML). When is set to , the class loads all the data returned by the object into memory before sending it to the server. - - - The main advantage of enabling streaming is reducing memory usage during bulk copy of max data types. - + + is `true`, reads from an object using , +optimizing memory usage by using the streaming capabilities. Streaming is only applicable to max data types (i.e. +VARBINARY(MAX), VARCHAR(MAX), NVARCHAR(MAX), and XML). When is set to false, +the class loads all the data returned by the object into memory before sending it to the server. + +> [!NOTE] +> The main advantage of enabling streaming is reducing memory usage during bulk copy of max data types. + +]]> @@ -879,7 +292,7 @@ - If has not been set when is called, an is thrown. If is modified while a operation is running, the change does not affect the current operation. The new value is used the next time a method is called. + If has not been set when is called, an is thrown. If is modified while a operation is running, the change does not affect the current operation. The new value is used the next time a method is called. is a three-part name (<database>.<owningschema>.<name>). You can qualify the table name with its database and owning schema if you choose. However, if the table name uses an underscore ("_") or any other special characters, you must escape the name using surrounding brackets as in ([<database>.<owningschema>.<name_01>]). @@ -889,104 +302,21 @@ - - The following console application demonstrates how to bulk load data using a connection that is already open. The destination table is a table in the AdventureWorks database. - - - In this example, the connection is first used to read data from a SQL Server table to a instance. The source data does not have to be located on SQL Server; you can use any data source that can be read to an or loaded to a . - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, ProductNumber FROM Production.Product;", - sourceConnection); - SqlDataReader reader = commandSourceData.ExecuteReader(); - - // Open the destination connection. In the real world you would - // not use SqlBulkCopy to move data from one table to the other - // in the same database. This is for demonstration purposes only. - using (SqlConnection destinationConnection = new SqlConnection(connectionString)) - { - destinationConnection.Open(); - - // Set up the bulk copy object. - // Note that the column positions in the source - // data reader match the column positions in - // the destination table so there is no need to - // map columns. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoMatchingColumns"; - - try - { - // Write from the source to the destination. - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + instance. The source data does not have to +be located on SQL Server; you can use any data source that can be read to an or loaded to a . + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.Single#1](~/../sqlclient/doc/samples/SqlBulkCopy_Single.cs#1)] + +]]> @@ -1005,105 +335,23 @@ - - The following console application demonstrates how to bulk load data using a connection that is already open. The property is set so that the event handler is called after every 50 rows copied to the table. - - - In this example, the connection is first used to read data from a SQL Server table to a instance. Then a second connection is opened to bulk copy the data. Note that the source data does not have to be located on SQL Server; you can use any data source that can be read to an or loaded to a . - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("NotifyAfter Sample"); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", - sourceConnection); - SqlDataReader reader = commandSourceData.ExecuteReader(); - - // Create the SqlBulkCopy object using a connection string. - // In the real world you would not use SqlBulkCopy to move - // data from one table to the other in the same database. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoMatchingColumns"; - - // Set up the event handler to notify after 50 rows. - bulkCopy.SqlRowsCopied += new SqlRowsCopiedEventHandler(OnSqlRowsCopied); - bulkCopy.NotifyAfter = 50; - - try - { - // Write from the source to the destination. - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static void OnSqlRowsCopied(object sender, SqlRowsCopiedEventArgs e) - { - Console.WriteLine("Copied {0} so far...", e.RowsCopied); - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + property is set so that +the event handler is called after every 50 rows copied to the table. + +In this example, the connection is first used to read data from a SQL Server table to a instance. Then a second connection is opened to bulk copy the data. +Note that the source data does not have to be located on SQL Server; you can use any data source that can be read to an or loaded to a . + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` +statement to copy the data. + +[!code-csharp[SqlBulkCopy.NotifyAfter#1](~/../sqlclient/doc/samples/SqlBulkCopy_NotifyAfter.cs#1)] + +]]> @@ -1122,105 +370,23 @@ - - The following console application demonstrates how to bulk load data using a connection that is already open. The property is set so that the event handler is called after every 50 rows copied to the table. - - - In this example, the connection is first used to read data from a SQL Server table to a instance. Note that the source data does not have to be located on SQL Server; you can use any data source that can be read to an or loaded to a . - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("NotifyAfter Sample"); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", - sourceConnection); - SqlDataReader reader = commandSourceData.ExecuteReader(); - - // Create the SqlBulkCopy object using a connection string. - // In the real world you would not use SqlBulkCopy to move - // data from one table to the other in the same database. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoMatchingColumns"; - - // Set up the event handler to notify after 50 rows. - bulkCopy.SqlRowsCopied += new SqlRowsCopiedEventHandler(OnSqlRowsCopied); - bulkCopy.NotifyAfter = 50; - - try - { - // Write from the source to the destination. - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static void OnSqlRowsCopied(object sender, SqlRowsCopiedEventArgs e) - { - Console.WriteLine("Copied {0} so far...", e.RowsCopied); - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + property is set so that +the event handler is called after every 50 rows copied to the table. + +In this example, the connection is first used to read data from a SQL Server table to a instance. Note that the source data does not have to be located on +SQL Server; you can use any data source that can be read to an or loaded to a . + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier +and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.NotifyAfter#1](~/../sqlclient/doc/samples/SqlBulkCopy_NotifyAfter.cs#1)] + +]]> @@ -1263,15 +429,19 @@ Releases all resources used by the current instance of the class. - - Call Dispose when you are finished using the . The Dispose method leaves the in an unusable state. After calling Dispose, you must release all references to the so the garbage collector can reclaim the memory that the was occupying. - - - For more information, see Cleaning Up Unmanaged Resources and Implementing a Dispose Method. - - - Always call Dispose before you release your last reference to the . Otherwise, the resources it is using will not be freed until the garbage collector calls the object's Finalize method. - + + . The `Dispose` method leaves the in an unusable state. +After calling `Dispose`, you must release all references to the so the garbage collector can reclaim the memory that the + was occupying. + +For more information, see [Cleaning Up Unmanaged Resources](https://learn.microsoft.com/dotnet/standard/garbage-collection/unmanaged) and +[Implementing a Dispose Method](https://learn.microsoft.com/dotnet/standard/garbage-collection/implementing-dispose). + +> [!NOTE] +> Always call `Dispose` before you release your last reference to the . Otherwise, the resources it is using will not be freed until the garbage collector calls the object's `Finalize` method. + +]]> @@ -1294,10 +464,10 @@ - The copy operation starts at the next available row in the reader. Most of the time, the reader was just returned by or a similar call, so the next available row is the first row. To process multiple results, call on the data reader and call again. + The copy operation starts at the next available row in the reader. Most of the time, the reader was just returned by or a similar call, so the next available row is the first row. To process multiple results, call on the data reader and call again. - Note that using modifies the state of the reader. The method will call until it returns false, the operation is aborted, or an error occurs. This means that the data reader will be in a different state, probably at the end of the result set, when the operation is complete. + Note that using modifies the state of the reader. The method will call until it returns false, the operation is aborted, or an error occurs. This means that the data reader will be in a different state, probably at the end of the result set, when the operation is complete. While the bulk copy operation is in progress, the associated destination is busy serving it, and no other operations can be performed on the connection. @@ -1307,91 +477,19 @@ - - The following console application demonstrates how to bulk load data from a . The destination table is a table in the AdventureWorks database. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, ProductNumber FROM Production.Product;", - sourceConnection); - SqlDataReader reader = commandSourceData.ExecuteReader(); - - // Set up the bulk copy object using a connection string. - // In the real world you would not use SqlBulkCopy to move - // data from one table to the other in the same database. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoMatchingColumns"; - - try - { - // Write from the source to the destination. - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + . The destination table is a table in the **AdventureWorks** database. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a +Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ConnectionString#1](~/../sqlclient/doc/samples/SqlBulkCopy_ConnectionString.cs#1)] + +]]> A did not specify a valid destination column name. @@ -1416,131 +514,20 @@ - - The following Console application demonstrates how to bulk load data from a . The destination table is a table in the AdventureWorks database. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - In this example, a is created at run time and is the source of the SqlBulkCopy operation. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a connection to the AdventureWorks database. - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoMatchingColumns;", - connection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Create a table with some rows. - DataTable newProducts = MakeTable(); - - // Create the SqlBulkCopy object. - // Note that the column positions in the source DataTable - // match the column positions in the destination table so - // there is no need to map columns. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoMatchingColumns"; - - try - { - // Write from the source to the destination. - bulkCopy.WriteToServer(newProducts); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32(commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static DataTable MakeTable() - { - // Create a new DataTable named NewProducts. - DataTable newProducts = new DataTable("NewProducts"); - - // Add three column objects to the table. - DataColumn productID = new DataColumn(); - productID.DataType = System.Type.GetType("System.Int32"); - productID.ColumnName = "ProductID"; - productID.AutoIncrement = true; - newProducts.Columns.Add(productID); - - DataColumn productName = new DataColumn(); - productName.DataType = System.Type.GetType("System.String"); - productName.ColumnName = "Name"; - newProducts.Columns.Add(productName); - - DataColumn productNumber = new DataColumn(); - productNumber.DataType = System.Type.GetType("System.String"); - productNumber.ColumnName = "ProductNumber"; - newProducts.Columns.Add(productNumber); - - // Create an array for DataColumn objects. - DataColumn[] keys = new DataColumn[1]; - keys[0] = productID; - newProducts.PrimaryKey = keys; - - // Add some new rows to the collection. - DataRow row = newProducts.NewRow(); - row["Name"] = "CC-101-WH"; - row["ProductNumber"] = "Cyclocomputer - White"; - - newProducts.Rows.Add(row); - row = newProducts.NewRow(); - row["Name"] = "CC-101-BK"; - row["ProductNumber"] = "Cyclocomputer - Black"; - - newProducts.Rows.Add(row); - row = newProducts.NewRow(); - row["Name"] = "CC-101-ST"; - row["ProductNumber"] = "Cyclocomputer - Stainless"; - newProducts.Rows.Add(row); - newProducts.AcceptChanges(); - - // Return the new DataTable. - return newProducts; - } - - private static string GetConnectionString() - { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + . The destination table is a table in the **AdventureWorks** database. + +In this example, a is created at run time and is the source of the `SqlBulkCopy` operation. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.DataTable#1](~/../sqlclient/doc/samples/SqlBulkCopy_DataTable.cs#1)] + +]]> A did not specify a valid destination column name. @@ -1563,147 +550,37 @@ Copies only rows that match the supplied row state in the supplied to a destination table specified by the property of the object. - - Only rows in the that are in the states indicated in the argument and have not been deleted are copied to the destination table. - - - If is specified, any , , and rows will also be copied to the server. No exception will be raised. - - - While the bulk copy operation is in progress, the associated destination is busy serving it, and no other operations can be performed on the connection. - - - The collection maps from the columns to the destination database table. - + + that are in the states indicated in the `rowState` argument and have not been deleted are copied to the destination table. + +> [!NOTE] +> If is specified, any , , and +rows will also be copied to the server. No exception will be raised. + +While the bulk copy operation is in progress, the associated destination is busy serving it, and no other operations can be performed on the connection. + +The collection maps from the columns to the destination database table. + +]]> + - - The following Console application demonstrates how to bulk load only the rows in a that match a specified state. In this case, only unchanged rows are added. The destination table is a table in the AdventureWorks database. - - - In this example, a is created at run time and three rows are added to it. Before the method is executed, one of the rows is edited. The method is called with a argument, so only the two unchanged rows are bulk copied to the destination. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a connection to the AdventureWorks database. - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoMatchingColumns;", - connection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Create a table with some rows. - DataTable newProducts = MakeTable(); - - // Make a change to one of the rows in the DataTable. - DataRow row = newProducts.Rows[0]; - row.BeginEdit(); - row["Name"] = "AAA"; - row.EndEdit(); - - // Create the SqlBulkCopy object. - // Note that the column positions in the source DataTable - // match the column positions in the destination table so - // there is no need to map columns. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoMatchingColumns"; - - try - { - // Write unchanged rows from the source to the destination. - bulkCopy.WriteToServer(newProducts, DataRowState.Unchanged); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32(commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static DataTable MakeTable() - { - // Create a new DataTable named NewProducts. - DataTable newProducts = new DataTable("NewProducts"); - - // Add three column objects to the table. - DataColumn productID = new DataColumn(); - productID.DataType = System.Type.GetType("System.Int32"); - productID.ColumnName = "ProductID"; - productID.AutoIncrement = true; - newProducts.Columns.Add(productID); - - DataColumn productName = new DataColumn(); - productName.DataType = System.Type.GetType("System.String"); - productName.ColumnName = "Name"; - newProducts.Columns.Add(productName); - - DataColumn productNumber = new DataColumn(); - productNumber.DataType = System.Type.GetType("System.String"); - productNumber.ColumnName = "ProductNumber"; - newProducts.Columns.Add(productNumber); - - // Create an array for DataColumn objects. - DataColumn[] keys = new DataColumn[1]; - keys[0] = productID; - newProducts.PrimaryKey = keys; - - // Add some new rows to the collection. - DataRow row = newProducts.NewRow(); - row["Name"] = "CC-101-WH"; - row["ProductNumber"] = "Cyclocomputer - White"; - - newProducts.Rows.Add(row); - row = newProducts.NewRow(); - row["Name"] = "CC-101-BK"; - row["ProductNumber"] = "Cyclocomputer - Black"; - - newProducts.Rows.Add(row); - row = newProducts.NewRow(); - row["Name"] = "CC-101-ST"; - row["ProductNumber"] = "Cyclocomputer - Stainless"; - newProducts.Rows.Add(row); - newProducts.AcceptChanges(); - - // Return the new DataTable. - return newProducts; - } - private static string GetConnectionString() - { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + that match a specified state. In this case, only unchanged rows are added. The destination table is a table in the **AdventureWorks** database. + +In this example, a is created at run time and three rows are added to it. Before the method is executed, one of the rows is edited. +The method is called with a `DataRowState.Unchanged` `rowState` argument, so only the two unchanged rows are bulk copied to the destination. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.DataRowState#1](~/../sqlclient/doc/samples/SqlBulkCopy_DataRowState.cs#1)] + +]]> A did not specify a valid destination column name. @@ -1725,132 +602,20 @@ - - The following console application demonstrates how to bulk load data from a array. The destination table is a table in the AdventureWorks database. - - - In this example, a is created at run time. A single row is selected from the to copy to the destination table. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a connection to the AdventureWorks database. - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoMatchingColumns;", - connection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Create a table with some rows. - DataTable newProducts = MakeTable(); - - // Get a reference to a single row in the table. - DataRow[] rowArray = newProducts.Select("Name='CC-101-BK'"); - - // Create the SqlBulkCopy object. - // Note that the column positions in the source DataTable - // match the column positions in the destination table so - // there is no need to map columns. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoMatchingColumns"; - - try - { - // Write the array of rows to the destination. - bulkCopy.WriteToServer(rowArray); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32(commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static DataTable MakeTable() - { - // Create a new DataTable named NewProducts. - DataTable newProducts = new DataTable("NewProducts"); - - // Add three column objects to the table. - DataColumn productID = new DataColumn(); - productID.DataType = System.Type.GetType("System.Int32"); - productID.ColumnName = "ProductID"; - productID.AutoIncrement = true; - newProducts.Columns.Add(productID); - - DataColumn productName = new DataColumn(); - productName.DataType = System.Type.GetType("System.String"); - productName.ColumnName = "Name"; - newProducts.Columns.Add(productName); - - DataColumn productNumber = new DataColumn(); - productNumber.DataType = System.Type.GetType("System.String"); - productNumber.ColumnName = "ProductNumber"; - newProducts.Columns.Add(productNumber); - - // Create an array for DataColumn objects. - DataColumn[] keys = new DataColumn[1]; - keys[0] = productID; - newProducts.PrimaryKey = keys; - - // Add some new rows to the collection. - DataRow row = newProducts.NewRow(); - row["Name"] = "CC-101-WH"; - row["ProductNumber"] = "Cyclocomputer - White"; - - newProducts.Rows.Add(row); - row = newProducts.NewRow(); - row["Name"] = "CC-101-BK"; - row["ProductNumber"] = "Cyclocomputer - Black"; - - newProducts.Rows.Add(row); - row = newProducts.NewRow(); - row["Name"] = "CC-101-ST"; - row["ProductNumber"] = "Cyclocomputer - Stainless"; - newProducts.Rows.Add(row); - newProducts.AcceptChanges(); - - // Return the new DataTable. - return newProducts; - } - - private static string GetConnectionString() - { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + array. The destination table is a table in the **AdventureWorks** database. + +In this example, a is created at run time. A single row is selected from the to copy to the destination table. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.RowArray#1](~/../sqlclient/doc/samples/SqlBulkCopy_RowArray.cs#1)] + +]]> A did not specify a valid destination column name. @@ -1872,12 +637,12 @@ - Calling multiple times for the same instance before task completion. Calling and for the same instance before task completion. - The connection drops or is closed during execution. - Returned in the task object, the object was closed during the method execution. - Returned in the task object, there was a connection pool timeout. - Returned in the task object, the object is closed before method execution. - A did not specify a valid destination column name. + Calling multiple times for the same instance before task completion. Calling and for the same instance before task completion. + The connection drops or is closed during execution. + Returned in the task object, the object was closed during the method execution. + Returned in the task object, there was a connection pool timeout. + Returned in the task object, the object is closed before method execution. + A did not specify a valid destination column name. @@ -1904,13 +669,13 @@ - Calling multiple times for the same instance before task completion. - Calling and for the same instance before task completion. - The connection drops or is closed during execution. - Returned in the task object, the object was closed during the method execution. - Returned in the task object, there was a connection pool timeout. - Returned in the task object, the object is closed before method execution. - A did not specify a valid destination column name. + Calling multiple times for the same instance before task completion. + Calling and for the same instance before task completion. + The connection drops or is closed during execution. + Returned in the task object, the object was closed during the method execution. + Returned in the task object, there was a connection pool timeout. + Returned in the task object, the object is closed before method execution. + A did not specify a valid destination column name. @@ -1933,15 +698,15 @@ - Calling multiple times for the same instance before task completion. - Calling and for the same instance before task completion. - The connection drops or is closed during execution. - Returned in the task object, the object was closed during the method execution. - Returned in the task object, there was a connection pool timeout. - Returned in the task object, the object is closed before method execution. - The was closed before the completed returned. - The 's associated connection was closed before the completed returned. - A did not specify a valid destination column name. + Calling multiple times for the same instance before task completion. + Calling and for the same instance before task completion. + The connection drops or is closed during execution. + Returned in the task object, the object was closed during the method execution. + Returned in the task object, there was a connection pool timeout. + Returned in the task object, the object is closed before method execution. + The was closed before the completed returned. + The 's associated connection was closed before the completed returned. + A did not specify a valid destination column name. @@ -1980,15 +745,15 @@ - Calling multiple times for the same instance before task completion. - Calling and for the same instance before task completion. - The connection drops or is closed during execution. - Returned in the task object, the object was closed during the method execution. - Returned in the task object, there was a connection pool timeout. - Returned in the task object, the object is closed before method execution. - The was closed before the completed returned. - The 's associated connection was closed before the completed returned. - A did not specify a valid destination column name. + Calling multiple times for the same instance before task completion. + Calling and for the same instance before task completion. + The connection drops or is closed during execution. + Returned in the task object, the object was closed during the method execution. + Returned in the task object, there was a connection pool timeout. + Returned in the task object, the object is closed before method execution. + The was closed before the completed returned. + The 's associated connection was closed before the completed returned. + A did not specify a valid destination column name. @@ -2015,15 +780,15 @@ - Calling multiple times for the same instance before task completion. - Calling and for the same instance before task completion. - The connection drops or is closed during execution. - Returned in the task object, the object was closed during the method execution. - Returned in the task object, there was a connection pool timeout. - Returned in the task object, the object is closed before method execution. - The was closed before the completed returned. - The 's associated connection was closed before the completed returned. - A did not specify a valid destination column name. + Calling multiple times for the same instance before task completion. + Calling and for the same instance before task completion. + The connection drops or is closed during execution. + Returned in the task object, the object was closed during the method execution. + Returned in the task object, there was a connection pool timeout. + Returned in the task object, the object is closed before method execution. + The was closed before the completed returned. + The 's associated connection was closed before the completed returned. + A did not specify a valid destination column name. @@ -2046,13 +811,13 @@ - Calling multiple times for the same instance before task completion. - Calling and for the same instance before task completion. - The connection drops or is closed during execution. - Returned in the task object, the object was closed during the method execution. - Returned in the task object, there was a connection pool timeout. - Returned in the task object, the object is closed before method execution. - A did not specify a valid destination column name. + Calling multiple times for the same instance before task completion. + Calling and for the same instance before task completion. + The connection drops or is closed during execution. + Returned in the task object, the object was closed during the method execution. + Returned in the task object, there was a connection pool timeout. + Returned in the task object, the object is closed before method execution. + A did not specify a valid destination column name. @@ -2079,13 +844,13 @@ - Calling multiple times for the same instance before task completion. - Calling and for the same instance before task completion. - The connection drops or is closed during execution. - Returned in the task object, the object was closed during the method execution. - Returned in the task object, there was a connection pool timeout. - Returned in the task object, the object is closed before method execution. - A did not specify a valid destination column name. + Calling multiple times for the same instance before task completion. + Calling and for the same instance before task completion. + The connection drops or is closed during execution. + Returned in the task object, the object was closed during the method execution. + Returned in the task object, there was a connection pool timeout. + Returned in the task object, the object is closed before method execution. + A did not specify a valid destination column name. @@ -2111,13 +876,13 @@ - Calling multiple times for the same instance before task completion. - Calling and for the same instance before task completion. - The connection drops or is closed during execution. - Returned in the task object, the object was closed during the method execution. - Returned in the task object, there was a connection pool timeout. - Returned in the task object, the object is closed before method execution. - A did not specify a valid destination column name. + Calling multiple times for the same instance before task completion. + Calling and for the same instance before task completion. + The connection drops or is closed during execution. + Returned in the task object, the object was closed during the method execution. + Returned in the task object, there was a connection pool timeout. + Returned in the task object, the object is closed before method execution. + A did not specify a valid destination column name. @@ -2147,13 +912,13 @@ - Calling multiple times for the same instance before task completion. - Calling and for the same instance before task completion. - The connection drops or is closed during execution. - Returned in the task object, the object was closed during the method execution. - Returned in the task object, there was a connection pool timeout. - Returned in the task object, the object is closed before method execution. - A did not specify a valid destination column name. + Calling multiple times for the same instance before task completion. + Calling and for the same instance before task completion. + The connection drops or is closed during execution. + Returned in the task object, the object was closed during the method execution. + Returned in the task object, there was a connection pool timeout. + Returned in the task object, the object is closed before method execution. + A did not specify a valid destination column name. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnMapping.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnMapping.xml index 9ff6c6427d..28418a2348 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnMapping.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnMapping.xml @@ -5,117 +5,31 @@ Defines the mapping between a column in a instance's data source and a column in the instance's destination table. - - Column mappings define the mapping between data source and the target table. - - - If mappings are not defined - that is, the collection is empty - the columns are mapped implicitly based on ordinal position. For this to work, source and target schemas must match. If they do not, an will be thrown. - - - If the collection is not empty, not every column present in the data source has to be specified. Those not mapped by the collection are ignored. - - - You can refer to source and target columns by either name or ordinal. You can also mix by-name and by-ordinal column references in the same mappings collection. - + + collection is empty - the columns are mapped implicitly based on ordinal position. For this to work, source and target schemas must match. If they do not, an will be thrown. + +If the collection is not empty, not every column present in the data source has to be specified. Those not mapped by the collection are ignored. + +You can refer to source and target columns by either name or ordinal. You can also mix by-name and by-ordinal column references in the same mappings collection. + +]]> - - The following example bulk copies data from a source table in the AdventureWorks - sample database to a destination table in the same database. Although the number of columns in the destination matches the number of columns in the source, and each destination column is in the same ordinal position as its corresponding source column, the column names do not match. objects are used to create a column map for the bulk copy. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for usingSqlBulkCopyonly. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoDifferentColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, ProductNumber FROM Production.Product;", - sourceConnection); - SqlDataReader reader = commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoDifferentColumns"; - - // Set up the column mappings by name. - SqlBulkCopyColumnMapping mapID = - new SqlBulkCopyColumnMapping("ProductID", "ProdID"); - bulkCopy.ColumnMappings.Add(mapID); - - SqlBulkCopyColumnMapping mapName = - new SqlBulkCopyColumnMapping("Name", "ProdName"); - bulkCopy.ColumnMappings.Add(mapName); - - SqlBulkCopyColumnMapping mapNumber = - new SqlBulkCopyColumnMapping("ProductNumber", "ProdNum"); - bulkCopy.ColumnMappings.Add(mapNumber); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32(commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + objects are used to create a column map for the bulk copy. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMapping#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMapping.cs#1)] + +]]> @@ -126,102 +40,16 @@ If you use this constructor, you must then define the source for the mapping using the property or the property, and define the destination for the mapping using the property or the property. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. Although the number of columns in the destination matches the number of columns in the source, the column names and ordinal positions do not match. objects are used to create a column map for the bulk copy. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoDifferentColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, ProductNumber FROM Production.Product;", - sourceConnection); - SqlDataReader reader = commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoDifferentColumns"; - - // Set up the column mappings by name. - SqlBulkCopyColumnMapping mapID = - new SqlBulkCopyColumnMapping("ProductID", "ProdID"); - bulkCopy.ColumnMappings.Add(mapID); - - SqlBulkCopyColumnMapping mapName = - new SqlBulkCopyColumnMapping("Name", "ProdName"); - bulkCopy.ColumnMappings.Add(mapName); - - SqlBulkCopyColumnMapping mapNumber = - new SqlBulkCopyColumnMapping("ProductNumber", "ProdNum"); - bulkCopy.ColumnMappings.Add(mapNumber); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32(commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + objects are used to create a column map for the bulk copy. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMapping#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMapping.cs#1)] + +]]> @@ -235,107 +63,17 @@ Creates a new column mapping, using column ordinals to refer to source and destination columns. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. Although the number of columns in the destination matches the number of columns in the source, the column names and ordinal positions do not match. objects are used to create a column map for the bulk copy based on the ordinal positions of the columns. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = - new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoDifferentColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = - new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoDifferentColumns"; - - // Set up the column mappings by ordinal. - SqlBulkCopyColumnMapping columnMapID = - new SqlBulkCopyColumnMapping(0, 0); - bulkCopy.ColumnMappings.Add(columnMapID); - - SqlBulkCopyColumnMapping columnMapName = - new SqlBulkCopyColumnMapping(1, 2); - bulkCopy.ColumnMappings.Add(columnMapName); - - SqlBulkCopyColumnMapping columnMapNumber = - new SqlBulkCopyColumnMapping(2, 1); - bulkCopy.ColumnMappings.Add(columnMapNumber); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + objects are used to create a column map for the bulk copy based on the ordinal positions of the columns. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMappingOrdinal#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMappingOrdinal.cs#1)] +]]> @@ -349,107 +87,18 @@ Creates a new column mapping, using a column ordinal to refer to the source column and a column name for the target column. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. Although the number of columns in the destination matches the number of columns in the source, the column names and ordinal positions do not match. objects are used to create a column map for the bulk copy. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoDifferentColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoDifferentColumns"; - - // Set up the column mappings by ordinal and name. - SqlBulkCopyColumnMapping columnMapID = - new SqlBulkCopyColumnMapping(0, "ProdID"); - bulkCopy.ColumnMappings.Add(columnMapID); - - SqlBulkCopyColumnMapping columnMapName = - new SqlBulkCopyColumnMapping(1, "ProdName"); - bulkCopy.ColumnMappings.Add(columnMapName); - - SqlBulkCopyColumnMapping columnMapNumber = - new SqlBulkCopyColumnMapping(2, "ProdNum"); - bulkCopy.ColumnMappings.Add(columnMapNumber); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + objects are used to create a column map for the bulk copy. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMappingOrdinalName#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMappingOrdinalName.cs#1)] + +]]> @@ -463,106 +112,18 @@ Creates a new column mapping, using a column name to refer to the source column and a column ordinal for the target column. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. Although the number of columns in the destination matches the number of columns in the source, the column names and ordinal positions do not match. objects are used to create a column map for the bulk copy. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoDifferentColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoDifferentColumns"; - - // Set up the column mappings by name and ordinal. - SqlBulkCopyColumnMapping columnMapID = - new SqlBulkCopyColumnMapping("ProductID", 0); - bulkCopy.ColumnMappings.Add(columnMapID); - - SqlBulkCopyColumnMapping columnMapName = - new SqlBulkCopyColumnMapping("Name", 2); - bulkCopy.ColumnMappings.Add(columnMapName); - - SqlBulkCopyColumnMapping columnMapNumber = - new SqlBulkCopyColumnMapping("ProductNumber", 1); - bulkCopy.ColumnMappings.Add(columnMapNumber); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + objects are used to create a column map for the bulk copy. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMappingNameOrdinal#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMappingNameOrdinal.cs#1)] + +]]> @@ -576,102 +137,18 @@ Creates a new column mapping, using column names to refer to source and destination columns. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. Although the number of columns in the destination matches the number of columns in the source, the column names and ordinal positions do not match. objects are used to create a column map for the bulk copy. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoDifferentColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, ProductNumber FROM Production.Product;", - sourceConnection); - SqlDataReader reader = commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoDifferentColumns"; - - // Set up the column mappings by name. - SqlBulkCopyColumnMapping mapID = - new SqlBulkCopyColumnMapping("ProductID", "ProdID"); - bulkCopy.ColumnMappings.Add(mapID); - - SqlBulkCopyColumnMapping mapName = - new SqlBulkCopyColumnMapping("Name", "ProdName"); - bulkCopy.ColumnMappings.Add(mapName); - - SqlBulkCopyColumnMapping mapNumber = - new SqlBulkCopyColumnMapping("ProductNumber", "ProdNum"); - bulkCopy.ColumnMappings.Add(mapNumber); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32(commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + objects are used to create a column map for the bulk copy. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMapping#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMapping.cs#1)] + +]]> @@ -682,112 +159,21 @@ The string value of the property. - The and properties are mutually exclusive. The last value set takes precedence. + The and properties are mutually exclusive. The last value set takes precedence. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. Although the number of columns in the destination matches the number of columns in the source, the column names and ordinal positions do not match. objects are used to create a column map for the bulk copy. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoDifferentColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoDifferentColumns"; - - // Set up the column mappings source and destination. - SqlBulkCopyColumnMapping mapID = new SqlBulkCopyColumnMapping(); - mapID.SourceColumn = "ProductID"; - mapID.DestinationColumn = "ProdID"; - bulkCopy.ColumnMappings.Add(mapID); - - SqlBulkCopyColumnMapping mapName = new SqlBulkCopyColumnMapping(); - mapName.SourceColumn = "Name"; - mapName.DestinationColumn = "ProdName"; - bulkCopy.ColumnMappings.Add(mapName); - - SqlBulkCopyColumnMapping mapNumber = new SqlBulkCopyColumnMapping(); - mapNumber.SourceColumn = "ProductNumber"; - mapNumber.DestinationColumn = "ProdNum"; - bulkCopy.ColumnMappings.Add(mapNumber); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + objects are used to create a column map for the bulk copy. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMappingDestinationColumn#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMappingDestinationColumn.cs#1)] + +]]> @@ -798,112 +184,21 @@ The integer value of the property, or -1 if the property has not been set. - The and properties are mutually exclusive. The last value set takes precedence. + The and properties are mutually exclusive. The last value set takes precedence. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. Although the number of columns in the destination matches the number of columns in the source, the column names and ordinal positions do not match. objects are used to create a column map for the bulk copy. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoDifferentColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoDifferentColumns"; - - // Set up the column mappings source and destination. - SqlBulkCopyColumnMapping mapID = new SqlBulkCopyColumnMapping(); - mapID.SourceOrdinal = 0; - mapID.DestinationOrdinal = 0; - bulkCopy.ColumnMappings.Add(mapID); - - SqlBulkCopyColumnMapping mapName = new SqlBulkCopyColumnMapping(); - mapName.SourceOrdinal = 1; - mapName.DestinationOrdinal = 2; - bulkCopy.ColumnMappings.Add(mapName); - - SqlBulkCopyColumnMapping mapNumber = new SqlBulkCopyColumnMapping(); - mapNumber.SourceOrdinal = 2; - mapNumber.DestinationOrdinal = 1; - bulkCopy.ColumnMappings.Add(mapNumber); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + objects are used to create a column map for the bulk copy. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMappingDestinationOrdinal#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMappingDestinationOrdinal.cs#1)] + +]]> @@ -914,112 +209,21 @@ The string value of the property. - The and properties are mutually exclusive. The last value set takes precedence. + The and properties are mutually exclusive. The last value set takes precedence. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. Although the number of columns in the destination matches the number of columns in the source, the column names and ordinal positions do not match. objects are used to create a column map for the bulk copy. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoDifferentColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoDifferentColumns"; - - // Set up the column mappings source and destination. - SqlBulkCopyColumnMapping mapID = new SqlBulkCopyColumnMapping(); - mapID.SourceColumn = "ProductID"; - mapID.DestinationColumn = "ProdID"; - bulkCopy.ColumnMappings.Add(mapID); - - SqlBulkCopyColumnMapping mapName = new SqlBulkCopyColumnMapping(); - mapName.SourceColumn = "Name"; - mapName.DestinationColumn = "ProdName"; - bulkCopy.ColumnMappings.Add(mapName); - - SqlBulkCopyColumnMapping mapNumber = new SqlBulkCopyColumnMapping(); - mapNumber.SourceColumn = "ProductNumber"; - mapNumber.DestinationColumn = "ProdNum"; - bulkCopy.ColumnMappings.Add(mapNumber); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + objects are used to create a column map for the bulk copy. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMappingDestinationColumn#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMappingDestinationColumn.cs#1)] + +]]> @@ -1030,112 +234,21 @@ The integer value of the property. - The and properties are mutually exclusive. The last value set takes precedence. + The and properties are mutually exclusive. The last value set takes precedence. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. Although the number of columns in the destination matches the number of columns in the source, the column names and ordinal positions do not match. objects are used to create a column map for the bulk copy. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoDifferentColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoDifferentColumns"; - - // Set up the column mappings source and destination. - SqlBulkCopyColumnMapping mapID = new SqlBulkCopyColumnMapping(); - mapID.SourceOrdinal = 0; - mapID.DestinationOrdinal = 0; - bulkCopy.ColumnMappings.Add(mapID); - - SqlBulkCopyColumnMapping mapName = new SqlBulkCopyColumnMapping(); - mapName.SourceOrdinal = 1; - mapName.DestinationOrdinal = 2; - bulkCopy.ColumnMappings.Add(mapName); - - SqlBulkCopyColumnMapping mapNumber = new SqlBulkCopyColumnMapping(); - mapNumber.SourceOrdinal = 2; - mapNumber.DestinationOrdinal = 1; - bulkCopy.ColumnMappings.Add(mapNumber); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + objects are used to create a column map for the bulk copy. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMappingDestinationOrdinal#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMappingDestinationOrdinal.cs#1)] + +]]> diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnMappingCollection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnMappingCollection.xml index b810a4ebbd..81673fe27b 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnMappingCollection.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnMappingCollection.xml @@ -5,113 +5,31 @@ Collection of objects that inherits from . - - Column mappings define the mapping between data source and the target table. - - - If mappings are not defined - that is, the collection is empty - the columns are mapped implicitly based on ordinal position. For this to work, source and target schemas must match. If they do not, an is thrown. - - - If the collection is not empty, not every column present in the data source has to be specified. Those not mapped by the collection are ignored. - - - You can refer to source and target columns by either name or ordinal. You can mix by-name and by-ordinal column references in the same mappings collection. - + + collection is empty - the columns are mapped implicitly based on ordinal position. For this to work, source and target schemas must match. If they do not, an is thrown. + +If the collection is not empty, not every column present in the data source has to be specified. Those not mapped by the collection are ignored. + +You can refer to source and target columns by either name or ordinal. You can mix by-name and by-ordinal column references in the same mappings collection. + +]]> - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. Although the number of columns in the destination matches the number of columns in the source, the column names and ordinal positions do not match. are added to the for the object to create a column map for the bulk copy. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoDifferentColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoDifferentColumns"; - - // The column order in the source doesn't match the order - // in the destination, so ColumnMappings must be defined. - bulkCopy.ColumnMappings.Add("ProductID", "ProdID"); - bulkCopy.ColumnMappings.Add("Name", "ProdName"); - bulkCopy.ColumnMappings.Add("ProductNumber", "ProdNum"); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + are added to the for the object to create a column map for the bulk copy. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMappingCollection#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMappingCollection.cs#1)] + +]]> @@ -125,106 +43,18 @@ A object. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. Although the number of columns in the destination matches the number of columns in the source, the column names and ordinal positions do not match. objects are used to create a column map for the bulk copy. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoDifferentColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoDifferentColumns"; - - // Set up the column mappings by name. - SqlBulkCopyColumnMapping mapID = - new SqlBulkCopyColumnMapping("ProductID", "ProdID"); - bulkCopy.ColumnMappings.Add(mapID); - - SqlBulkCopyColumnMapping mapName = - new SqlBulkCopyColumnMapping("Name", "ProdName"); - bulkCopy.ColumnMappings.Add(mapName); - - SqlBulkCopyColumnMapping mapNumber = - new SqlBulkCopyColumnMapping("ProductNumber", "ProdNum"); - bulkCopy.ColumnMappings.Add(mapNumber); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + objects are used to create a column map for the bulk copy. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMapping#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMapping.cs)] + +]]> @@ -241,102 +71,21 @@ A column mapping. - Mappings in a collection must be uniform: either all integer/integer pairs, all string/string pairs, all integer/string pairs, or all string/integer pairs. If you try to add a mapping that is different from others already in the collection, an is thrown. + Mappings in a collection must be uniform: either all integer/integer pairs, all string/string pairs, all integer/string pairs, or all string/integer pairs. If you try to add a mapping that is different from others already in the collection, an is thrown. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. Although the number of columns in the destination matches the number of columns in the source, the column names and ordinal positions do not match. objects are used to create a column map for the bulk copy using the ordinal position of the source and destination columns. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoDifferentColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoDifferentColumns"; - - // The column order in the source doesn't match the order - // in the destination, so ColumnMappings must be defined. - bulkCopy.ColumnMappings.Add(0, 0); - bulkCopy.ColumnMappings.Add(1, 2); - bulkCopy.ColumnMappings.Add(2, 1); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + objects are used to create a column map for the bulk copy using the ordinal position of the source and destination columns. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMappingCollectionOrdinal#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMappingCollectionOrdinal.cs#1)] + +]]> @@ -353,102 +102,21 @@ A column mapping. - Mappings in a collection must be uniform: either all integer/integer pairs, all string/string pairs, all integer/string pairs, or all string/integer pairs. If you try to add a mapping that is different from others already in the collection, an is thrown. + Mappings in a collection must be uniform: either all integer/integer pairs, all string/string pairs, all integer/string pairs, or all string/integer pairs. If you try to add a mapping that is different from others already in the collection, an is thrown. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. Although the number of columns in the destination matches the number of columns in the source, the column names and ordinal positions do not match. objects are used to create a column map for the bulk copy. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoDifferentColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoDifferentColumns"; - - // The column order in the source doesn't match the order - // in the destination, so ColumnMappings must be defined. - bulkCopy.ColumnMappings.Add(0, 0); - bulkCopy.ColumnMappings.Add(1, 2); - bulkCopy.ColumnMappings.Add(2, 1); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + objects are used to create a column map for the bulk copy. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy_ColumnMappingIndexColName#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMappingIndexColName.cs#1)] + +]]> @@ -465,102 +133,21 @@ A column mapping. - Mappings in a collection must be uniform: either all integer/integer pairs, all string/string pairs, all integer/string pairs, or all string/integer pairs. If you try to add a mapping that is different from others already in the collection, an is thrown. + Mappings in a collection must be uniform: either all integer/integer pairs, all string/string pairs, all integer/string pairs, or all string/integer pairs. If you try to add a mapping that is different from others already in the collection, an is thrown. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. Although the number of columns in the destination matches the number of columns in the source, the column names and ordinal positions do not match. objects are used to create a column map for the bulk copy. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoDifferentColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoDifferentColumns"; - - // The column order in the source doesn't match the order - // in the destination, so ColumnMappings must be defined. - bulkCopy.ColumnMappings.Add(0, 0); - bulkCopy.ColumnMappings.Add(1, 2); - bulkCopy.ColumnMappings.Add(2, 1); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + objects are used to create a column map for the bulk copy. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMappingColNameIndex#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMappingColNameIndex.cs#1)] + +]]> @@ -580,99 +167,18 @@ Mappings in a collection must be uniform: either all integer/integer pairs, all string/string pairs, all integer/string pairs, or all string/integer pairs. If you try to add a mapping that is different from others already in the collection, an is thrown. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. Although the number of columns in the destination matches the number of columns in the source, the column names and ordinal positions do not match. The code creates a object by specifying the column names. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoDifferentColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoDifferentColumns"; - - // The column order in the source doesn't match the order - // in the destination, so ColumnMappings must be defined. - bulkCopy.ColumnMappings.Add("ProductID", "ProdID"); - bulkCopy.ColumnMappings.Add("Name", "ProdName"); - bulkCopy.ColumnMappings.Add("ProductNumber", "ProdNum"); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + object by specifying the column names. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMappingCollection#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMappingCollection.cs#1)] + +]]> @@ -680,190 +186,27 @@ Clears the contents of the collection. - - The method is most commonly used when you use a single instance to process more than one bulk copy operation. If you create column mappings for one bulk copy operation, you must clear the after the method and before processing the next bulk copy. - - - Performing several bulk copies using the same instance will usually be more efficient from a performance point of view than using a separate for each operation. - + + method is most commonly used when you use a single instance to process more than one bulk copy operation. If you create column mappings for one bulk copy operation, you must clear the after the method and before processing the next bulk copy. + +Performing several bulk copies using the same instance will usually be more efficient from a performance point of view than using a separate for each operation. + +]]> - - The following example performs two bulk copy operations. The first operation copies sales order header information, and the second copies sales order details. Although not strictly necessary in this example (because the ordinal positions of the source and destination columns do match), the example defines column mappings for each bulk copy operation. The method must be used after the first bulk copy is performed and before the next bulk copy's column mappings are defined. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a connection to the AdventureWorks database. - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - // Empty the destination tables. - SqlCommand deleteHeader = new SqlCommand( - "DELETE FROM dbo.BulkCopyDemoOrderHeader;", - connection); - deleteHeader.ExecuteNonQuery(); - SqlCommand deleteDetail = new SqlCommand( - "DELETE FROM dbo.BulkCopyDemoOrderDetail;", - connection); - deleteDetail.ExecuteNonQuery(); - - // Perform an initial count on the destination - // table with matching columns. - SqlCommand countRowHeader = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderHeader;", - connection); - long countStartHeader = System.Convert.ToInt32( - countRowHeader.ExecuteScalar()); - Console.WriteLine( - "Starting row count for Header table = {0}", - countStartHeader); - - // Perform an initial count on the destination - // table with different column positions. - SqlCommand countRowDetail = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderDetail;", - connection); - long countStartDetail = System.Convert.ToInt32( - countRowDetail.ExecuteScalar()); - Console.WriteLine( - "Starting row count for Detail table = {0}", - countStartDetail); - - // Get data from the source table as a SqlDataReader. - // The Sales.SalesOrderHeader and Sales.SalesOrderDetail - // tables are quite large and could easily cause a timeout - // if all data from the tables is added to the destination. - // To keep the example simple and quick, a parameter is - // used to select only orders for a particular account - // as the source for the bulk insert. - SqlCommand headerData = new SqlCommand( - "SELECT [SalesOrderID], [OrderDate], " + - "[AccountNumber] FROM [Sales].[SalesOrderHeader] " + - "WHERE [AccountNumber] = @accountNumber;", - connection); - SqlParameter parameterAccount = new SqlParameter(); - parameterAccount.ParameterName = "@accountNumber"; - parameterAccount.SqlDbType = SqlDbType.NVarChar; - parameterAccount.Direction = ParameterDirection.Input; - parameterAccount.Value = "10-4020-000034"; - headerData.Parameters.Add(parameterAccount); - SqlDataReader readerHeader = headerData.ExecuteReader(); - - // Get the Detail data in a separate connection. - using (SqlConnection connection2 = new SqlConnection(connectionString)) - { - connection2.Open(); - SqlCommand sourceDetailData = new SqlCommand( - "SELECT [Sales].[SalesOrderDetail].[SalesOrderID], [SalesOrderDetailID], " + - "[OrderQty], [ProductID], [UnitPrice] FROM [Sales].[SalesOrderDetail] " + - "INNER JOIN [Sales].[SalesOrderHeader] ON [Sales].[SalesOrderDetail]." + - "[SalesOrderID] = [Sales].[SalesOrderHeader].[SalesOrderID] " + - "WHERE [AccountNumber] = @accountNumber;", connection2); - - SqlParameter accountDetail = new SqlParameter(); - accountDetail.ParameterName = "@accountNumber"; - accountDetail.SqlDbType = SqlDbType.NVarChar; - accountDetail.Direction = ParameterDirection.Input; - accountDetail.Value = "10-4020-000034"; - sourceDetailData.Parameters.Add(accountDetail); - SqlDataReader readerDetail = sourceDetailData.ExecuteReader(); - - // Create the SqlBulkCopy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoOrderHeader"; - - // Guarantee that columns are mapped correctly by - // defining the column mappings for the order. - bulkCopy.ColumnMappings.Add("SalesOrderID", "SalesOrderID"); - bulkCopy.ColumnMappings.Add("OrderDate", "OrderDate"); - bulkCopy.ColumnMappings.Add("AccountNumber", "AccountNumber"); - - // Write readerHeader to the destination. - try - { - bulkCopy.WriteToServer(readerHeader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - readerHeader.Close(); - } - - // Set up the order details destination. - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoOrderDetail"; - - // Clear the ColumnMappingCollection. - bulkCopy.ColumnMappings.Clear(); - - // Add order detail column mappings. - bulkCopy.ColumnMappings.Add("SalesOrderID", "SalesOrderID"); - bulkCopy.ColumnMappings.Add("SalesOrderDetailID", "SalesOrderDetailID"); - bulkCopy.ColumnMappings.Add("OrderQty", "OrderQty"); - bulkCopy.ColumnMappings.Add("ProductID", "ProductID"); - bulkCopy.ColumnMappings.Add("UnitPrice", "UnitPrice"); - - // Write readerDetail to the destination. - try - { - bulkCopy.WriteToServer(readerDetail); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - readerDetail.Close(); - } - } - - // Perform a final count on the destination - // tables to see how many rows were added. - long countEndHeader = System.Convert.ToInt32( - countRowHeader.ExecuteScalar()); - Console.WriteLine("{0} rows were added to the Header table.", - countEndHeader - countStartHeader); - long countEndDetail = System.Convert.ToInt32( - countRowDetail.ExecuteScalar()); - Console.WriteLine("{0} rows were added to the Detail table.", - countEndDetail - countStartDetail); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - } - - private static string GetConnectionString() - { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + method must be used after the first bulk copy is performed and before the next bulk copy's column mappings are defined. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMappingOrdersDetails#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMappingOrdersDetails.cs#1)] + +]]> @@ -923,191 +266,27 @@ Removes the specified element from the . - - The method is most commonly used when you use a single instance to process more than one bulk copy operation. If you create column mappings for one bulk copy operation, you must remove mappings that no longer apply after the method is called and before defining mapping for the next bulk copy. You can clear the entire collection by using the method, or remove mappings individually using the method or the method. - - - Performing several bulk copies using the same instance will usually be more efficient from a performance point of view than using a separate for each operation. - + + method is most commonly used when you use a single instance to process more than one bulk copy operation. If you create column mappings for one bulk copy operation, you must remove mappings that no longer apply after the method is called and before defining mapping for the next bulk copy. You can clear the entire collection by using the method, or remove mappings individually using the method or the method. + +Performing several bulk copies using the same instance will usually be more efficient from a performance point of view than using a separate for each operation. + +]]> - - The following example performs two bulk copy operations. The first operation copies sales order header information, and the second copies sales order details. Although not strictly necessary in this example (because the ordinal positions of the source and destination columns do match), the example defines column mappings for each bulk copy operation. Both bulk copies include a mapping for the SalesOrderID, so rather than clearing the entire collection between bulk copy operations, the example removes all mappings except for the SalesOrderID mapping and then adds the appropriate mappings for the second bulk copy operation. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a connection to the AdventureWorks database. - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - // Empty the destination tables. - SqlCommand deleteHeader = new SqlCommand( - "DELETE FROM dbo.BulkCopyDemoOrderHeader;", - connection); - deleteHeader.ExecuteNonQuery(); - SqlCommand deleteDetail = new SqlCommand( - "DELETE FROM dbo.BulkCopyDemoOrderDetail;", - connection); - deleteDetail.ExecuteNonQuery(); - - // Perform an initial count on the destination - // table with matching columns. - SqlCommand countRowHeader = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderHeader;", - connection); - long countStartHeader = System.Convert.ToInt32( - countRowHeader.ExecuteScalar()); - Console.WriteLine( - "Starting row count for Header table = {0}", - countStartHeader); - - // Perform an initial count on the destination - // table with different column positions. - SqlCommand countRowDetail = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderDetail;", - connection); - long countStartDetail = System.Convert.ToInt32( - countRowDetail.ExecuteScalar()); - Console.WriteLine( - "Starting row count for Detail table = {0}", - countStartDetail); - - // Get data from the source table as a SqlDataReader. - // The Sales.SalesOrderHeader and Sales.SalesOrderDetail - // tables are quite large and could easily cause a timeout - // if all data from the tables is added to the destination. - // To keep the example simple and quick, a parameter is - // used to select only orders for a particular account - // as the source for the bulk insert. - SqlCommand headerData = new SqlCommand( - "SELECT [SalesOrderID], [OrderDate], " + - "[AccountNumber] FROM [Sales].[SalesOrderHeader] " + - "WHERE [AccountNumber] = @accountNumber;", - connection); - SqlParameter parameterAccount = new SqlParameter(); - parameterAccount.ParameterName = "@accountNumber"; - parameterAccount.SqlDbType = SqlDbType.NVarChar; - parameterAccount.Direction = ParameterDirection.Input; - parameterAccount.Value = "10-4020-000034"; - headerData.Parameters.Add(parameterAccount); - SqlDataReader readerHeader = headerData.ExecuteReader(); - - // Get the Detail data in a separate connection. - using (SqlConnection connection2 = new SqlConnection(connectionString)) - { - connection2.Open(); - SqlCommand sourceDetailData = new SqlCommand( - "SELECT [Sales].[SalesOrderDetail].[SalesOrderID], [SalesOrderDetailID], " + - "[OrderQty], [ProductID], [UnitPrice] FROM [Sales].[SalesOrderDetail] " + - "INNER JOIN [Sales].[SalesOrderHeader] ON [Sales].[SalesOrderDetail]." + - "[SalesOrderID] = [Sales].[SalesOrderHeader].[SalesOrderID] " + - "WHERE [AccountNumber] = @accountNumber;", connection2); - - SqlParameter accountDetail = new SqlParameter(); - accountDetail.ParameterName = "@accountNumber"; - accountDetail.SqlDbType = SqlDbType.NVarChar; - accountDetail.Direction = ParameterDirection.Input; - accountDetail.Value = "10-4020-000034"; - sourceDetailData.Parameters.Add(accountDetail); - SqlDataReader readerDetail = sourceDetailData.ExecuteReader(); - - // Create the SqlBulkCopy object. - using (SqlBulkCopy bulkCopy = - new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoOrderHeader"; - - // Guarantee that columns are mapped correctly by - // defining the column mappings for the order. - bulkCopy.ColumnMappings.Add("SalesOrderID", "SalesOrderID"); - bulkCopy.ColumnMappings.Add("OrderDate", "OrderDate"); - bulkCopy.ColumnMappings.Add("AccountNumber", "AccountNumber"); - - // Write readerHeader to the destination. - try - { - bulkCopy.WriteToServer(readerHeader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - readerHeader.Close(); - } - - // Set up the order details destination. - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoOrderDetail"; - - // Clear the ColumnMappingCollection. - bulkCopy.ColumnMappings.Clear(); - - // Add order detail column mappings. - bulkCopy.ColumnMappings.Add("SalesOrderID", "SalesOrderID"); - bulkCopy.ColumnMappings.Add("SalesOrderDetailID", "SalesOrderDetailID"); - bulkCopy.ColumnMappings.Add("OrderQty", "OrderQty"); - bulkCopy.ColumnMappings.Add("ProductID", "ProductID"); - bulkCopy.ColumnMappings.Add("UnitPrice", "UnitPrice"); - - // Write readerDetail to the destination. - try - { - bulkCopy.WriteToServer(readerDetail); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - readerDetail.Close(); - } - } - - // Perform a final count on the destination - // tables to see how many rows were added. - long countEndHeader = System.Convert.ToInt32( - countRowHeader.ExecuteScalar()); - Console.WriteLine("{0} rows were added to the Header table.", - countEndHeader - countStartHeader); - long countEndDetail = System.Convert.ToInt32( - countRowDetail.ExecuteScalar()); - Console.WriteLine("{0} rows were added to the Detail table.", - countEndDetail - countStartDetail); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - } - - private static string GetConnectionString() - { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMappingRemove#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMappingRemove.cs#1)] + +]]> @@ -1118,193 +297,27 @@ Removes the mapping at the specified index from the collection. - - The method is most commonly used when you use a single instance to process more than one bulk copy operation. If you create column mappings for one bulk copy operation, you must remove mappings that no longer apply after the method is called and before defining mapping for the next bulk copy. You can clear the entire collection by using the method, or remove mappings individually using the method or the method. - - - Performing several bulk copies using the same instance will usually be more efficient from a performance point of view than using a separate for each operation. - + + method is most commonly used when you use a single instance to process more than one bulk copy operation. If you create column mappings for one bulk copy operation, you must remove mappings that no longer apply after the method is called and before defining mapping for the next bulk copy. You can clear the entire collection by using the method, or remove mappings individually using the method or the method. + +Performing several bulk copies using the same instance will usually be more efficient from a performance point of view than using a separate for each operation. + +]]> - - The following example performs two bulk copy operations. The first operation copies sales order header information, and the second copies sales order details. Although not strictly necessary in this example (because the ordinal positions of the source and destination columns do match), the example defines column mappings for each bulk copy operation. Both bulk copies include a mapping for the SalesOrderID, so rather than clearing the entire collection between bulk copy operations, the example removes all mappings except for the SalesOrderID mapping and then adds the appropriate mappings for the second bulk copy operation. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a connection to the AdventureWorks database. - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - // Empty the destination tables. - SqlCommand deleteHeader = new SqlCommand( - "DELETE FROM dbo.BulkCopyDemoOrderHeader;", - connection); - deleteHeader.ExecuteNonQuery(); - SqlCommand deleteDetail = new SqlCommand( - "DELETE FROM dbo.BulkCopyDemoOrderDetail;", - connection); - deleteDetail.ExecuteNonQuery(); - - // Perform an initial count on the destination - // table with matching columns. - SqlCommand countRowHeader = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderHeader;", - connection); - long countStartHeader = System.Convert.ToInt32( - countRowHeader.ExecuteScalar()); - Console.WriteLine( - "Starting row count for Header table = {0}", - countStartHeader); - - // Perform an initial count on the destination - // table with different column positions. - SqlCommand countRowDetail = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderDetail;", - connection); - long countStartDetail = System.Convert.ToInt32( - countRowDetail.ExecuteScalar()); - Console.WriteLine( - "Starting row count for Detail table = {0}", - countStartDetail); - - // Get data from the source table as a SqlDataReader. - // The Sales.SalesOrderHeader and Sales.SalesOrderDetail - // tables are quite large and could easily cause a timeout - // if all data from the tables is added to the destination. - // To keep the example simple and quick, a parameter is - // used to select only orders for a particular account - // as the source for the bulk insert. - SqlCommand headerData = new SqlCommand( - "SELECT [SalesOrderID], [OrderDate], " + - "[AccountNumber] FROM [Sales].[SalesOrderHeader] " + - "WHERE [AccountNumber] = @accountNumber;", - connection); - SqlParameter parameterAccount = new SqlParameter(); - parameterAccount.ParameterName = "@accountNumber"; - parameterAccount.SqlDbType = SqlDbType.NVarChar; - parameterAccount.Direction = ParameterDirection.Input; - parameterAccount.Value = "10-4020-000034"; - headerData.Parameters.Add(parameterAccount); - SqlDataReader readerHeader = headerData.ExecuteReader(); - - // Get the Detail data in a separate connection. - using (SqlConnection connection2 = new SqlConnection(connectionString)) - { - connection2.Open(); - SqlCommand sourceDetailData = new SqlCommand( - "SELECT [Sales].[SalesOrderDetail].[SalesOrderID], [SalesOrderDetailID], " + - "[OrderQty], [ProductID], [UnitPrice] FROM [Sales].[SalesOrderDetail] " + - "INNER JOIN [Sales].[SalesOrderHeader] ON [Sales].[SalesOrderDetail]." + - "[SalesOrderID] = [Sales].[SalesOrderHeader].[SalesOrderID] " + - "WHERE [AccountNumber] = @accountNumber;", connection2); - - SqlParameter accountDetail = new SqlParameter(); - accountDetail.ParameterName = "@accountNumber"; - accountDetail.SqlDbType = SqlDbType.NVarChar; - accountDetail.Direction = ParameterDirection.Input; - accountDetail.Value = "10-4020-000034"; - sourceDetailData.Parameters.Add(accountDetail); - SqlDataReader readerDetail = sourceDetailData.ExecuteReader(); - - // Create the SqlBulkCopy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoOrderHeader"; - - // Guarantee that columns are mapped correctly by - // defining the column mappings for the order. - bulkCopy.ColumnMappings.Add("SalesOrderID", "SalesOrderID"); - bulkCopy.ColumnMappings.Add("OrderDate", "OrderDate"); - bulkCopy.ColumnMappings.Add("AccountNumber", "AccountNumber"); - - // Write readerHeader to the destination. - try - { - bulkCopy.WriteToServer(readerHeader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - readerHeader.Close(); - } - - // Set up the order details destination. - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoOrderDetail"; - - // Rather than clearing mappings that are not necessary - // for the next bulk copy operation, the unneeded mappings - // are removed with the RemoveAt method. - bulkCopy.ColumnMappings.RemoveAt(2); - bulkCopy.ColumnMappings.RemoveAt(1); - - // Add order detail column mappings. - bulkCopy.ColumnMappings.Add("SalesOrderDetailID", "SalesOrderDetailID"); - bulkCopy.ColumnMappings.Add("OrderQty", "OrderQty"); - bulkCopy.ColumnMappings.Add("ProductID", "ProductID"); - bulkCopy.ColumnMappings.Add("UnitPrice", "UnitPrice"); - bulkCopy.WriteToServer(readerDetail); - - // Write readerDetail to the destination. - try - { - bulkCopy.WriteToServer(readerDetail); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - readerDetail.Close(); - } - } - - // Perform a final count on the destination - // tables to see how many rows were added. - long countEndHeader = System.Convert.ToInt32( - countRowHeader.ExecuteScalar()); - Console.WriteLine("{0} rows were added to the Header table.", - countEndHeader - countStartHeader); - long countEndDetail = System.Convert.ToInt32( - countRowDetail.ExecuteScalar()); - Console.WriteLine("{0} rows were added to the Detail table.", - countEndDetail - countStartDetail); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - } - - private static string GetConnectionString() - { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnMappingRemoveAt#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnMappingRemoveAt.cs#1)] + +]]> diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnOrderHint.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnOrderHint.xml index 9a57501ce3..12d1783d1e 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnOrderHint.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnOrderHint.xml @@ -5,118 +5,35 @@ Defines the sort order for a column in a instance's destination table, according to the clustered index on the table. - - Column order hints define the sort order of the column in the destination table. - - - SqlBulkCopy's performance is improved if the data being imported is sorted according to the clustered index on the table, if any. If the data is sorted in an order that differs from the order of a clustered index key or if there is no clustered index on the table, the order hint is ignored. - - - Order hints can be specified for any number of columns in the destination table. By default, the bulk insert operation assumes the data is unordered if no hints are provided. - - - The column names supplied must be valid column names in the destination table. The order in which hints can be specified is arbitrary. A single column name cannot be specified more than once. - - - If the collection is not empty, order hints can only be provided for valid destination columns which have been mapped. - - - If a of Unspecified is given, an will be thrown. - + + collection is not empty, order hints can only be provided for valid destination columns which have been mapped. + +If a of Unspecified is given, an will be thrown. + +]]> - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. A SqlBulkCopyColumnOrderHint object is used to define the sort order for the ProductNumber destination column. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoMatchingColumns"; - - // Setup an order hint for the ProductNumber column. - SqlBulkCopyColumnOrderHint hintNumber = - new SqlBulkCopyColumnOrderHint("ProductNumber", SortOrder.Ascending); - bulkCopy.ColumnOrderHints.Add(hintNumber); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnOrderHint#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHint.cs#1)] + +]]> @@ -130,98 +47,18 @@ Creates a new column order hint for the specified destination column. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. A SqlBulkCopyColumnOrderHint object is used to define the sort order for the ProductNumber destination column. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoMatchingColumns"; - - // Setup an order hint for the ProductNumber column. - SqlBulkCopyColumnOrderHint hintNumber = - new SqlBulkCopyColumnOrderHint("ProductNumber", SortOrder.Ascending); - bulkCopy.ColumnOrderHints.Add(hintNumber); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnOrderHint#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHint.cs#1)] + +]]> @@ -235,100 +72,18 @@ An will be thrown if a null or empty string is given. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. A SqlBulkCopyColumnOrderHint object is used to define the sort order for the ProductNumber destination column. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoMatchingColumns"; - - // Setup an order hint for the ProductNumber column. - SqlBulkCopyColumnOrderHint hintNumber = - new SqlBulkCopyColumnOrderHint("number", SortOrder.Ascending); - hintNumber.Column = "ProductNumber"; - bulkCopy.ColumnOrderHints.Add(hintNumber); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnOrderHintColumn#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHintColumn.cs#1)] + +]]> The value is null or empty. @@ -342,102 +97,21 @@ The SortOrder value of the property. - An will be thrown if a of Unspecified is given. + An will be thrown if a of Unspecified is given. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. A SqlBulkCopyColumnOrderHint object is used to define the sort order for the ProductNumber destination column. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoMatchingColumns"; - - // Setup an order hint for the ProductNumber column. - SqlBulkCopyColumnOrderHint hintNumber = - new SqlBulkCopyColumnOrderHint("ProductNumber", SortOrder.Ascending); - hintNumber.SortOrder = SortOrder.Descending; - bulkCopy.ColumnOrderHints.Add(hintNumber); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnOrderHintSortOrder#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHintSortOrder.cs#1)] + +]]> The sort order cannot be unspecified for a column order hint. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnOrderHintCollection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnOrderHintCollection.xml index da4d5ff534..b94d1f2aeb 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnOrderHintCollection.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyColumnOrderHintCollection.xml @@ -5,117 +5,35 @@ Collection of objects that inherits from . - - Column order hints define the sort order of the column in the destination table. - - - SqlBulkCopy's performance is improved if the data being imported is sorted according to the clustered index on the table, if any. If the data is sorted in an order that differs from the order of a clustered index key or if there is no clustered index on the table, the order hint is ignored. - - - Order hints can be specified for any number of columns in the destination table. By default, the bulk insert operation assumes the data is unordered if no hints are provided. - - - The column names supplied must be valid column names in the destination table. The order in which hints can be specified is arbitrary. A single column name cannot be specified more than once. - - - If the collection is not empty, order hints can only be provided for valid destination columns which have been mapped. - - - If a of Unspecified is given, an will be thrown. - + + collection is not empty, order hints can only be provided for valid destination columns which have been mapped. + +If a of Unspecified is given, an will be thrown. + +]]> - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. s are added to the of the object to specify order hints for the bulk copy operation. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoMatchingColumns"; - - // Specify the sort order for the ProductNumber column in - // the destination table. - bulkCopy.ColumnOrderHints.Add("ProductNumber", SortOrder.Ascending); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + s are added to the of the object to specify order hints for the bulk copy operation. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnOrderHintCollection#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHintCollection.cs#1)] + +]]> @@ -129,100 +47,19 @@ A object. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. A SqlBulkCopyColumnOrderHint object is used to define the sort order for the ProductNumber destination column. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoMatchingColumns"; - - // Specify the sort order for the ProductNumber column in - // the destination table. - // Setup an order hint for the ProductNumber column. - SqlBulkCopyColumnOrderHint hintNumber = - new SqlBulkCopyColumnOrderHint("ProductNumber", SortOrder.Ascending); - bulkCopy.ColumnOrderHints.Add(hintNumber); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + object is used to define the sort order for the **ProductNumber** destination column. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnOrderHintCollectionAdd#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionAdd.cs)] + +]]> + The value is null. @@ -242,98 +79,19 @@ A column column order hint. - - The following example bulk copies data from a source table in the AdventureWorks sample database to a destination table in the same database. A SqlBulkCopyColumnOrderHint object is added to the by providing the destination column name and its sort order. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = - commandSourceData.ExecuteReader(); - - // Set up the bulk copy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoMatchingColumns"; - - // Specify the sort order for the ProductNumber column in - // the destination table. - bulkCopy.ColumnOrderHints.Add("ProductNumber", SortOrder.Ascending); - - // Write from the source to the destination. - try - { - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + object is added to the by providing the destination column name and its sort order. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a +Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnOrderHintCollectionAdd2#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionAdd2.cs#1)] + +]]> @@ -341,199 +99,27 @@ Clears the contents of the collection. - - The method is most commonly used when you use a single instance to process more than one bulk copy operation. If you create column order hints for one bulk copy operation, you must clear the after the method and before processing the next bulk copy. - - - Performing several bulk copies using the same instance will usually be more efficient from a performance point of view than using a separate for each operation. - + + method is most commonly used when you use a single instance to process more than one bulk copy operation. If you create column order hints for one bulk copy operation, you must clear the after the method and before processing the next bulk copy. + +Performing several bulk copies using the same instance will usually be more efficient from a performance point of view than using a separate for each operation. + +]]> - - The following example performs two bulk copy operations. The first operation copies sales order header information, and the second copies sales order details. The example defines a column order hint for each bulk copy operation. The method must be used after the first bulk copy is performed and before the next bulk copy's order hint is defined. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a connection to the AdventureWorks database. - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - // Empty the destination tables. - SqlCommand deleteHeader = new SqlCommand( - "DELETE FROM dbo.BulkCopyDemoOrderHeader;", - connection); - deleteHeader.ExecuteNonQuery(); - SqlCommand deleteDetail = new SqlCommand( - "DELETE FROM dbo.BulkCopyDemoOrderDetail;", - connection); - deleteDetail.ExecuteNonQuery(); - - // Perform an initial count on the destination - // table with matching columns. - SqlCommand countRowHeader = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderHeader;", - connection); - long countStartHeader = System.Convert.ToInt32( - countRowHeader.ExecuteScalar()); - Console.WriteLine( - "Starting row count for Header table = {0}", - countStartHeader); - - // Perform an initial count on the destination - // table with different column positions. - SqlCommand countRowDetail = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderDetail;", - connection); - long countStartDetail = System.Convert.ToInt32( - countRowDetail.ExecuteScalar()); - Console.WriteLine( - "Starting row count for Detail table = {0}", - countStartDetail); - - // Get data from the source table as a SqlDataReader. - // The Sales.SalesOrderHeader and Sales.SalesOrderDetail - // tables are quite large and could easily cause a timeout - // if all data from the tables is added to the destination. - // To keep the example simple and quick, a parameter is - // used to select only orders for a particular account - // as the source for the bulk insert. - SqlCommand headerData = new SqlCommand( - "SELECT [SalesOrderID], [OrderDate], " + - "[AccountNumber] FROM [Sales].[SalesOrderHeader] " + - "WHERE [AccountNumber] = @accountNumber;", - connection); - SqlParameter parameterAccount = new SqlParameter(); - parameterAccount.ParameterName = "@accountNumber"; - parameterAccount.SqlDbType = SqlDbType.NVarChar; - parameterAccount.Direction = ParameterDirection.Input; - parameterAccount.Value = "10-4020-000034"; - headerData.Parameters.Add(parameterAccount); - SqlDataReader readerHeader = headerData.ExecuteReader(); - - // Get the Detail data in a separate connection. - using (SqlConnection connection2 = new SqlConnection(connectionString)) - { - connection2.Open(); - SqlCommand sourceDetailData = new SqlCommand( - "SELECT [Sales].[SalesOrderDetail].[SalesOrderID], [SalesOrderDetailID], " + - "[OrderQty], [ProductID], [UnitPrice] FROM [Sales].[SalesOrderDetail] " + - "INNER JOIN [Sales].[SalesOrderHeader] ON [Sales].[SalesOrderDetail]." + - "[SalesOrderID] = [Sales].[SalesOrderHeader].[SalesOrderID] " + - "WHERE [AccountNumber] = @accountNumber;", connection2); - - SqlParameter accountDetail = new SqlParameter(); - accountDetail.ParameterName = "@accountNumber"; - accountDetail.SqlDbType = SqlDbType.NVarChar; - accountDetail.Direction = ParameterDirection.Input; - accountDetail.Value = "10-4020-000034"; - sourceDetailData.Parameters.Add(accountDetail); - SqlDataReader readerDetail = sourceDetailData.ExecuteReader(); - - // Create the SqlBulkCopy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoOrderHeader"; - - // Guarantee that columns are mapped correctly by - // defining the column mappings for the order. - bulkCopy.ColumnMappings.Add("SalesOrderID", "SalesOrderID"); - bulkCopy.ColumnMappings.Add("OrderDate", "OrderDate"); - bulkCopy.ColumnMappings.Add("AccountNumber", "AccountNumber"); - - // Add order hint for OrderDate column. - bulkCopy.ColumnOrderHints.Add("OrderDate", SortOrder.Ascending); - - // Write readerHeader to the destination. - try - { - bulkCopy.WriteToServer(readerHeader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - readerHeader.Close(); - } - - // Set up the order details destination. - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoOrderDetail"; - - // Clear the ColumnMappingCollection. - bulkCopy.ColumnMappings.Clear(); - - // Add order detail column mappings. - bulkCopy.ColumnMappings.Add("SalesOrderID", "SalesOrderID"); - bulkCopy.ColumnMappings.Add("SalesOrderDetailID", "SalesOrderDetailID"); - bulkCopy.ColumnMappings.Add("OrderQty", "OrderQty"); - bulkCopy.ColumnMappings.Add("ProductID", "ProductID"); - bulkCopy.ColumnMappings.Add("UnitPrice", "UnitPrice"); - - // Clear the ColumnOrderHintCollection. - bulkCopy.ColumnOrderHints.Clear(); - - // Add order hint for SalesOrderID column. - bulkCopy.ColumnOrderHints.Add("SalesOrderID", SortOrder.Ascending); - - // Write readerDetail to the destination. - try - { - bulkCopy.WriteToServer(readerDetail); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - readerDetail.Close(); - } - } - - // Perform a final count on the destination - // tables to see how many rows were added. - long countEndHeader = System.Convert.ToInt32( - countRowHeader.ExecuteScalar()); - Console.WriteLine("{0} rows were added to the Header table.", - countEndHeader - countStartHeader); - long countEndDetail = System.Convert.ToInt32( - countRowDetail.ExecuteScalar()); - Console.WriteLine("{0} rows were added to the Detail table.", - countEndDetail - countStartDetail); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - } - - private static string GetConnectionString() - { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + method must be used after the first bulk copy is performed and before the next bulk copy's order hint is defined. + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnOrderHintCollectionClear#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionClear.cs#1)] + +]]> @@ -609,202 +195,29 @@ Removes the specified element from the . - - The Remove method is most commonly used when you use a single instance to process more than one bulk copy operation. If you create column order hints for one bulk copy operation, you must clear the after the method and before processing the next bulk copy. - - - You can clear the entire collection by using the method, or remove hints individually using the Remove method or the method. - - - Performing several bulk copies using the same instance will usually be more efficient from a performance point of view than using a separate for each operation. - + + instance to process more than one bulk copy operation. If you create column order hints for one bulk copy operation, you must clear the after the method and before processing the next bulk copy. + +You can clear the entire collection by using the method, or remove hints individually using the Remove method or the method. + +Performing several bulk copies using the same instance will usually be more efficient from a performance point of view than using a separate for each operation. + +]]> - - The following example performs two bulk copy operations. The first operation copies sales order header information, and the second copies sales order details. The example defines a column order hint for the OrderDate column in the first bulk copy operation. The hint is removed before the second bulk copy operation. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); + + +> [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnOrderHintCollectionRemove#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionRemove.cs#1)] + +]]> The value is null. @@ -818,199 +231,28 @@ Removes the column order hint at the specified index from the collection. - - The method is most commonly used when you use a single instance to process more than one bulk copy operation. If you create column order hints for one bulk copy operation, you must clear the after the method and before processing the next bulk copy. You can clear the entire collection by using the method, or remove hints individually using the method or the method. - - - Performing several bulk copies using the same instance will usually be more efficient from a performance point of view than using a separate for each operation. - + + method is most commonly used when you use a single instance to process more than one bulk copy operation. If you create column order hints for one bulk copy operation, you must clear the after the method and before processing the next bulk copy. +You can clear the entire collection by using the method, or remove hints individually using the method or the method. + +Performing several bulk copies using the same instance will usually be more efficient from a performance point of view than using a separate for each operation. + +]]> - - The following example performs two bulk copy operations. The first operation copies sales order header information, and the second copies sales order details. The example defines a column order hint for the OrderDate column in the first bulk copy operation. The hint is removed before the second bulk copy operation. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - - // Open a connection to the AdventureWorks database. - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - // Empty the destination tables. - SqlCommand deleteHeader = new SqlCommand( - "DELETE FROM dbo.BulkCopyDemoOrderHeader;", - connection); - deleteHeader.ExecuteNonQuery(); - SqlCommand deleteDetail = new SqlCommand( - "DELETE FROM dbo.BulkCopyDemoOrderDetail;", - connection); - deleteDetail.ExecuteNonQuery(); - - // Perform an initial count on the destination - // table with matching columns. - SqlCommand countRowHeader = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderHeader;", - connection); - long countStartHeader = System.Convert.ToInt32( - countRowHeader.ExecuteScalar()); - Console.WriteLine( - "Starting row count for Header table = {0}", - countStartHeader); - - // Perform an initial count on the destination - // table with different column positions. - SqlCommand countRowDetail = new SqlCommand( - "SELECT COUNT(*) FROM dbo.BulkCopyDemoOrderDetail;", - connection); - long countStartDetail = System.Convert.ToInt32( - countRowDetail.ExecuteScalar()); - Console.WriteLine( - "Starting row count for Detail table = {0}", - countStartDetail); - - // Get data from the source table as a SqlDataReader. - // The Sales.SalesOrderHeader and Sales.SalesOrderDetail - // tables are quite large and could easily cause a timeout - // if all data from the tables is added to the destination. - // To keep the example simple and quick, a parameter is - // used to select only orders for a particular account - // as the source for the bulk insert. - SqlCommand headerData = new SqlCommand( - "SELECT [SalesOrderID], [OrderDate], " + - "[AccountNumber] FROM [Sales].[SalesOrderHeader] " + - "WHERE [AccountNumber] = @accountNumber;", - connection); - SqlParameter parameterAccount = new SqlParameter(); - parameterAccount.ParameterName = "@accountNumber"; - parameterAccount.SqlDbType = SqlDbType.NVarChar; - parameterAccount.Direction = ParameterDirection.Input; - parameterAccount.Value = "10-4020-000034"; - headerData.Parameters.Add(parameterAccount); - SqlDataReader readerHeader = headerData.ExecuteReader(); - - // Get the Detail data in a separate connection. - using (SqlConnection connection2 = new SqlConnection(connectionString)) - { - connection2.Open(); - SqlCommand sourceDetailData = new SqlCommand( - "SELECT [Sales].[SalesOrderDetail].[SalesOrderID], [SalesOrderDetailID], " + - "[OrderQty], [ProductID], [UnitPrice] FROM [Sales].[SalesOrderDetail] " + - "INNER JOIN [Sales].[SalesOrderHeader] ON [Sales].[SalesOrderDetail]." + - "[SalesOrderID] = [Sales].[SalesOrderHeader].[SalesOrderID] " + - "WHERE [AccountNumber] = @accountNumber;", connection2); - - SqlParameter accountDetail = new SqlParameter(); - accountDetail.ParameterName = "@accountNumber"; - accountDetail.SqlDbType = SqlDbType.NVarChar; - accountDetail.Direction = ParameterDirection.Input; - accountDetail.Value = "10-4020-000034"; - sourceDetailData.Parameters.Add(accountDetail); - SqlDataReader readerDetail = sourceDetailData.ExecuteReader(); - - // Create the SqlBulkCopy object. - using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoOrderHeader"; - - // Guarantee that columns are mapped correctly by - // defining the column mappings for the order. - bulkCopy.ColumnMappings.Add("SalesOrderID", "SalesOrderID"); - bulkCopy.ColumnMappings.Add("OrderDate", "OrderDate"); - bulkCopy.ColumnMappings.Add("AccountNumber", "AccountNumber"); - - // Add the order hint for the OrderDate column. - SqlBulkCopyColumnOrderHint hintDate = - new SqlBulkCopyColumnOrderHint("OrderDate", SortOrder.Ascending); - bulkCopy.ColumnOrderHints.Add(hintDate); - - // Write readerHeader to the destination. - try - { - bulkCopy.WriteToServer(readerHeader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - readerHeader.Close(); - } - - // Set up the order details destination. - bulkCopy.DestinationTableName = "dbo.BulkCopyDemoOrderDetail"; - - // Clear the ColumnMappingCollection. - bulkCopy.ColumnMappings.Clear(); - - // Add order detail column mappings. - bulkCopy.ColumnMappings.Add("SalesOrderID", "SalesOrderID"); - bulkCopy.ColumnMappings.Add("SalesOrderDetailID", "SalesOrderDetailID"); - bulkCopy.ColumnMappings.Add("OrderQty", "OrderQty"); - bulkCopy.ColumnMappings.Add("ProductID", "ProductID"); - bulkCopy.ColumnMappings.Add("UnitPrice", "UnitPrice"); - - // Remove the order hint for the OrderDate column. - bulkCopy.ColumnOrderHints.RemoveAt(0); - - // Write readerDetail to the destination. - try - { - bulkCopy.WriteToServer(readerDetail); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - readerDetail.Close(); - } - } - - // Perform a final count on the destination - // tables to see how many rows were added. - long countEndHeader = System.Convert.ToInt32( - countRowHeader.ExecuteScalar()); - Console.WriteLine("{0} rows were added to the Header table.", - countEndHeader - countStartHeader); - long countEndDetail = System.Convert.ToInt32( - countRowDetail.ExecuteScalar()); - Console.WriteLine("{0} rows were added to the Detail table.", - countEndDetail - countStartDetail); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - } - - private static string GetConnectionString() - { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). + +This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.ColumnOrderHintCollectionRemoveAt#1](~/../sqlclient/doc/samples/SqlBulkCopy_ColumnOrderHintCollectionRemoveAt.cs#1)] + +]]> The index must be non-negative and less than the size of the collection. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyOptions.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyOptions.xml index 987bbd40c9..a4ac472666 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyOptions.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlBulkCopyOptions.xml @@ -5,104 +5,22 @@ Bitwise flag that specifies one or more options to use with an instance of . - - You can use the enumeration when you construct a instance to change how the methods for that instance behave. - + You can use the enumeration when you construct a instance to change how the methods for that instance behave. - - The following console application demonstrates how to perform a bulk load that copies the value in the identity column of the source table to the corresponding column in the destination table, instead of generating a new value for each row's identity column. - - - To see how the option changes the way the bulk load works, run the sample with the dbo.BulkCopyDemoMatchingColumns table empty. All rows load from the source. Next, run the sample again without emptying the table. An exception is thrown, and the code writes a message to the console window notifying you that rows were not added because of primary key violations. - - - This sample will not run unless you have created the work tables as described in Bulk Copy Example Setup. - - - This code is provided to demonstrate the syntax for using SqlBulkCopy only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL INSERT … SELECT statement to copy the data. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = GetConnectionString(); - // Open a sourceConnection to the AdventureWorks database. - using (SqlConnection sourceConnection = new SqlConnection(connectionString)) - { - sourceConnection.Open(); - - // Perform an initial count on the destination table. - SqlCommand commandRowCount = new SqlCommand( - "SELECT COUNT(*) FROM " + - "dbo.BulkCopyDemoMatchingColumns;", - sourceConnection); - long countStart = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Starting row count = {0}", countStart); - - // Get data from the source table as a SqlDataReader. - SqlCommand commandSourceData = new SqlCommand( - "SELECT ProductID, Name, " + - "ProductNumber " + - "FROM Production.Product;", sourceConnection); - SqlDataReader reader = commandSourceData.ExecuteReader(); - - // Create the SqlBulkCopy object using a connection string - // and the KeepIdentity option. - // In the real world you would not use SqlBulkCopy to move - // data from one table to the other in the same database. - using (SqlBulkCopy bulkCopy = - new SqlBulkCopy(connectionString, SqlBulkCopyOptions.KeepIdentity)) - { - bulkCopy.DestinationTableName = - "dbo.BulkCopyDemoMatchingColumns"; - - try - { - // Write from the source to the destination. - bulkCopy.WriteToServer(reader); - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - finally - { - // Close the SqlDataReader. The SqlBulkCopy - // object is automatically closed at the end - // of the using block. - reader.Close(); - } - } - - // Perform a final count on the destination - // table to see how many rows were added. - long countEnd = System.Convert.ToInt32( - commandRowCount.ExecuteScalar()); - Console.WriteLine("Ending row count = {0}", countEnd); - Console.WriteLine("{0} rows were added.", countEnd - countStart); - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - } - - private static string GetConnectionString() - { - // To avoid storing the sourceConnection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=(local); " + - " Integrated Security=true;" + - "Initial Catalog=AdventureWorks;"; - } - } - + + [!IMPORTANT] +> This sample will not run unless you have created the work tables as described in [Bulk Copy Example Setup](https://learn.microsoft.com/sql/connect/ado-net/sql/bulk-copy-example-setup). This code is provided to demonstrate the syntax for using **SqlBulkCopy** only. If the source and destination tables are in the same SQL Server instance, it is easier and faster to use a Transact-SQL `INSERT … SELECT` statement to copy the data. + +[!code-csharp[SqlBulkCopy.KeepIdentity#1](~/../sqlclient/doc/samples/SqlBulkCopy_KeepIdentity.cs)] + + ]]> + diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlClientDiagnostic.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlClientDiagnostic.xml index 6306c624f4..d84a520c54 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlClientDiagnostic.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlClientDiagnostic.xml @@ -34,7 +34,7 @@ The element at the specified index in the read-only list. - + diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlClientPermission.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlClientPermission.xml index 02ada91280..1c7f629ff9 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlClientPermission.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlClientPermission.xml @@ -5,12 +5,14 @@ Enables the .NET Framework Data Provider for SQL Server to help make sure that a user has a security level sufficient to access a data source. - - The property takes precedence over the property. Therefore, if you set to , you must also set to to prevent a user from making a connection using a blank password. - - - When using code access security permissions for ADO.NET, the correct pattern is to start with the most restrictive case (no permissions at all) and then add the specific permissions that are needed for the particular task that the code needs to perform. The opposite pattern, starting with all permissions and then denying a specific permission, is not secure, because there are many ways of expressing the same connection string. For example, if you start with all permissions and then attempt to deny the use of the connection string "server=someserver", the string "server=someserver.mycompany.com" would still be allowed. By always starting by granting no permissions at all, you reduce the chances that there are holes in the permission set. - + + property takes precedence over the property. Therefore, if you set to `false`, you must also set to `false` to prevent a user from making a connection using a blank password. + +> [!NOTE] +> When using code access security permissions for ADO.NET, the correct pattern is to start with the most restrictive case (no permissions at all) and then add the specific permissions that are needed for the particular task that the code needs to perform. The opposite pattern, starting with all permissions and then denying a specific permission, is not secure, because there are many ways of expressing the same connection string. For example, if you start with all permissions and then attempt to deny the use of the connection string "server=someserver", the string "server=someserver.mycompany.com" would still be allowed. By always starting by granting no permissions at all, you reduce the chances that there are holes in the permission set. + ]]> + @@ -54,32 +56,31 @@ Adds a new connection string and a set of restricted keywords to the object. - - Use this method to configure which connection strings are allowed by a particular permission object. For example, use the following code fragment if you want to only allow a specific connection string and nothing else: - - - permission.Add("server=MyServer; database=MyDatabase; Integrated Security=true", "", KeyRestrictionBehavior.AllowOnly) - - - When using code access security permissions for ADO.NET, the correct pattern is to start with the most restrictive case (no permissions at all) and then add the specific permissions that are needed for the particular task that the code needs to perform. The opposite pattern, starting with all permissions and then trying to deny a specific permission, is not secure, because there are many ways of expressing the same connection string. For example, if you start with all permissions and then attempt to deny the use of the connection string "server=someserver", the string "server=someserver.mycompany.com" would still be allowed. By always starting by granting no permissions at all, you reduce the chances that there are holes in the permission set. - + + [!NOTE] +> When using code access security permissions for ADO.NET, the correct pattern is to start with the most restrictive case (no permissions at all) and then add the specific permissions that are +needed for the particular task that the code needs to perform. The opposite pattern, starting with all permissions and then trying to deny a specific permission, is not secure, because there are +many ways of expressing the same connection string. For example, if you start with all permissions and then attempt to deny the use of the connection string "server=someserver", +the string "server=someserver.mycompany.com" would still be allowed. By always starting by granting no permissions at all, you reduce the chances that there are holes in the permission set. + + ]]> + - - - The following example allows connection strings that use any database, but only on the server named MyServer, with any user and password combination and containing no other connection string keywords: - - - permission.Add("server=MyServer;", "database=; user id=; password=;", KeyRestrictionBehavior.AllowOnly) - - - - - The following example uses the same scenario as above but allows for a failover partner that can be used when connecting to servers configured for mirroring: - - - permission.Add("server=MyServer; failover partner=MyMirrorServer", "database=; user id=; password=;", KeyRestrictionBehavior.AllowOnly) - - @@ -97,7 +98,7 @@ An XML encoding of the security object, including any state information. - Custom code that extends security objects must implement the and methods to make the objects security encodable. + Custom code that extends security objects must implement the and methods to make the objects security encodable. @@ -108,7 +109,7 @@ Reconstructs a security object with a specified state from an XML encoding. - Custom code that extends security objects needs to implement the and methods to make the objects security encodable. + Custom code that extends security objects needs to implement the and methods to make the objects security encodable. @@ -122,7 +123,7 @@ A new permission object that represents the union of the current permission object and the specified permission object. - The result of a call to is a permission that represents all the operations permitted by both the current permission object and the specified permission object. Any demand that passes either permission passes their union. + The result of a call to is a permission that represents all the operations permitted by both the current permission object and the specified permission object. Any demand that passes either permission passes their union. The object is not the same type as the current permission object. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCngProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCngProvider.xml index b6dd7ffa5a..340d301242 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCngProvider.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCngProvider.xml @@ -66,7 +66,7 @@ The signature of the column master key metadata. - The method must be implemented by the corresponding key store providers. should use an asymmetric key identified by a key path and sign the master key metadata consisting of , , and . + The method must be implemented by the corresponding key store providers. should use an asymmetric key identified by a key path and sign the master key metadata consisting of , , and . diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCspProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCspProvider.xml index 975fd65722..1df2ec5dc3 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCspProvider.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionCspProvider.xml @@ -66,7 +66,7 @@ The signature of the column master key metadata. - The method must be implemented by the corresponding key store providers. should use an asymmetric key identified by a key path and sign the master key metadata consisting of , , and . + The method must be implemented by the corresponding key store providers. should use an asymmetric key identified by a key path and sign the master key metadata consisting of , , and . diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionKeyStoreProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionKeyStoreProvider.xml index 35be0d1817..401258f9e5 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionKeyStoreProvider.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlColumnEncryptionKeyStoreProvider.xml @@ -59,10 +59,10 @@ - To ensure that the method doesn't break applications that rely on an old API, it throws a exception by default. + To ensure that the method doesn't break applications that rely on an old API, it throws a exception by default. - The method will be used by client tools that generate Column Master Keys (CMK) for customers. must be implemented by the corresponding key store providers that wish to use enclaves with Always Encrypted. + The method will be used by client tools that generate Column Master Keys (CMK) for customers. must be implemented by the corresponding key store providers that wish to use enclaves with Always Encrypted. @@ -80,10 +80,10 @@ The signature of the column master key metadata. - When implemented in a derived class, this method is expected to verify the specified signature is valid for the column master key with the specified key path and the specified enclave behavior. The default implementation throws NotImplementedException. + When implemented in a derived class, this method is expected to verify the specified signature is valid for the column master key with the specified key path and the specified enclave behavior. The default implementation throws `NotImplementedException`. - When implemented in a derived class, the method is expected to return true if the specified signature is valid, or false if the specified signature is not valid. The default implementation throws NotImplementedException. + When implemented in a derived class, the method is expected to return true if the specified signature is valid, or false if the specified signature is not valid. The default implementation throws `NotImplementedException`. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml index b13fd28d87..6b269de720 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlCommand.xml @@ -5,448 +5,45 @@ Represents a Transact-SQL statement or stored procedure to execute against a SQL Server database. This class cannot be inherited. - - When an instance of is created, the read/write properties are set to their initial values. For a list of these values, see the constructor. - - - features the following methods for executing commands at a SQL Server database: - - - - Item - Description - - - - - Initiates the asynchronous execution of the Transact-SQL statement or stored procedure that is described by this , generally executing commands such as INSERT, DELETE, UPDATE, and SET statements. Each call to must be paired with a call to which finishes the operation, typically on a separate thread. - - - - - - Initiates the asynchronous execution of the Transact-SQL statement or stored procedure that is described by this and retrieves one or more results sets from the server. Each call to must be paired with a call to which finishes the operation, typically on a separate thread. - - - - - - Initiates the asynchronous execution of the Transact-SQL statement or stored procedure that is described by this . Each call to must be paired with a call to , which finishes the operation, typically on a separate thread, and returns an object. - - - - - - Executes commands that return rows. For increased performance, invokes commands using the Transact-SQL sp_executesql system stored procedure. Therefore, might not have the effect that you want if used to execute commands such as Transact-SQL SET statements. - - - - - Executes commands such as Transact-SQL INSERT, DELETE, UPDATE, and SET statements. - - - - Retrieves a single value (for example, an aggregate value) from a database. - - - - - Sends the to the and builds an object. - - - - - - You can reset the property and reuse the object. However, you must close the before you can execute a new or previous command. - - - If a is generated by the method executing a , the remains open when the severity level is 19 or less. When the severity level is 20 or greater, the server ordinarily closes the . However, the user can reopen the connection and continue. - - - - Nameless, also called ordinal, parameters are not supported by the .NET Framework Data Provider for SQL Server. - - - - - - The following example creates a , a , and a . The example reads through the data, writing it to the console. Finally, the example closes the and then the as it exits the using code blocks. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - namespace SqlCommandCs - { - class Program - { - static void Main() - { - string str = "Data Source=(local);Initial Catalog=Northwind;" - + "Integrated Security=SSPI"; - ReadOrderData(str); - } - - private static void ReadOrderData(string connectionString) - { - string queryString = "SELECT OrderID, CustomerID FROM dbo.Orders;"; - using (SqlConnection connection = new SqlConnection(connectionString)) - { - SqlCommand command = new SqlCommand(queryString, connection); - connection.Open(); - using (SqlDataReader reader = command.ExecuteReader()) - { - while (reader.Read()) - { - Console.WriteLine(String.Format("{0}, {1}", reader[0], reader[1])); - } - } - } - } - } - } + is created, the read/write properties are set to their initial values. For a list of these values, see the constructor. - - - The following sample shows how to create and execute different types of SqlCommand objects. - - - - USE [master] - GO - - CREATE DATABASE [MySchool] - GO - - USE [MySchool] - GO - - SET ANSI_NULLS ON - GO - SET QUOTED_IDENTIFIER ON - GO - CREATE procedure [dbo].[CourseExtInfo] @CourseId int - as - select c.CourseID,c.Title,c.Credits,d.Name as DepartmentName - from Course as c left outer join Department as d on c.DepartmentID=d.DepartmentID - where c.CourseID=@CourseId - - GO - - SET ANSI_NULLS ON - GO - SET QUOTED_IDENTIFIER ON - GO - create procedure [dbo].[DepartmentInfo] @DepartmentId int,@CourseCount int output - as - select @CourseCount=Count(c.CourseID) - from course as c - where c.DepartmentID=@DepartmentId - - select d.DepartmentID,d.Name,d.Budget,d.StartDate,d.Administrator - from Department as d - where d.DepartmentID=@DepartmentId - - GO - - SET ANSI_NULLS ON - GO - SET QUOTED_IDENTIFIER ON - GO - Create PROCEDURE [dbo].[GetDepartmentsOfSpecifiedYear] - @Year int,@BudgetSum money output - AS - BEGIN - SELECT @BudgetSum=SUM([Budget]) - FROM [MySchool].[dbo].[Department] - Where YEAR([StartDate])=@Year - - SELECT [DepartmentID] - ,[Name] - ,[Budget] - ,[StartDate] - ,[Administrator] - FROM [MySchool].[dbo].[Department] - Where YEAR([StartDate])=@Year - - END - GO - - SET ANSI_NULLS ON - GO - SET QUOTED_IDENTIFIER ON - GO - CREATE TABLE [dbo].[Course]([CourseID] [nvarchar](10) NOT NULL, - [Year] [smallint] NOT NULL, - [Title] [nvarchar](100) NOT NULL, - [Credits] [int] NOT NULL, - [DepartmentID] [int] NOT NULL, - CONSTRAINT [PK_Course] PRIMARY KEY CLUSTERED - ( - [CourseID] ASC, - [Year] ASC - )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY] - - GO - - SET ANSI_NULLS ON - GO - SET QUOTED_IDENTIFIER ON - GO - CREATE TABLE [dbo].[Department]([DepartmentID] [int] IDENTITY(1,1) NOT NULL, - [Name] [nvarchar](50) NOT NULL, - [Budget] [money] NOT NULL, - [StartDate] [datetime] NOT NULL, - [Administrator] [int] NULL, - CONSTRAINT [PK_Department] PRIMARY KEY CLUSTERED - ( - [DepartmentID] ASC - )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY] - - GO - - SET ANSI_NULLS ON - GO - SET QUOTED_IDENTIFIER ON - GO - CREATE TABLE [dbo].[Person]([PersonID] [int] IDENTITY(1,1) NOT NULL, - [LastName] [nvarchar](50) NOT NULL, - [FirstName] [nvarchar](50) NOT NULL, - [HireDate] [datetime] NULL, - [EnrollmentDate] [datetime] NULL, - CONSTRAINT [PK_School.Student] PRIMARY KEY CLUSTERED - ( - [PersonID] ASC - )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY] - - GO - - SET ANSI_NULLS ON - GO - SET QUOTED_IDENTIFIER ON - GO - CREATE TABLE [dbo].[StudentGrade]([EnrollmentID] [int] IDENTITY(1,1) NOT NULL, - [CourseID] [nvarchar](10) NOT NULL, - [StudentID] [int] NOT NULL, - [Grade] [decimal](3, 2) NOT NULL, - CONSTRAINT [PK_StudentGrade] PRIMARY KEY CLUSTERED - ( - [EnrollmentID] ASC - )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY] - - GO - - SET ANSI_NULLS ON - GO - SET QUOTED_IDENTIFIER ON - GO - create view [dbo].[EnglishCourse] - as - select c.CourseID,c.Title,c.Credits,c.DepartmentID - from Course as c join Department as d on c.DepartmentID=d.DepartmentID - where d.Name=N'English' - - GO - INSERT [dbo].[Course] ([CourseID], [Year], [Title], [Credits], [DepartmentID]) VALUES (N'C1045', 2012, N'Calculus', 4, 7) - INSERT [dbo].[Course] ([CourseID], [Year], [Title], [Credits], [DepartmentID]) VALUES (N'C1061', 2012, N'Physics', 4, 1) - INSERT [dbo].[Course] ([CourseID], [Year], [Title], [Credits], [DepartmentID]) VALUES (N'C2021', 2012, N'Composition', 3, 2) - INSERT [dbo].[Course] ([CourseID], [Year], [Title], [Credits], [DepartmentID]) VALUES (N'C2042', 2012, N'Literature', 4, 2) - SET IDENTITY_INSERT [dbo].[Department] ON - - INSERT [dbo].[Department] ([DepartmentID], [Name], [Budget], [StartDate], [Administrator]) VALUES (1, N'Engineering', 350000.0000, CAST(0x0000999C00000000 AS DateTime), 2) - INSERT [dbo].[Department] ([DepartmentID], [Name], [Budget], [StartDate], [Administrator]) VALUES (2, N'English', 120000.0000, CAST(0x0000999C00000000 AS DateTime), 6) - INSERT [dbo].[Department] ([DepartmentID], [Name], [Budget], [StartDate], [Administrator]) VALUES (4, N'Economics', 200000.0000, CAST(0x0000999C00000000 AS DateTime), 4) - INSERT [dbo].[Department] ([DepartmentID], [Name], [Budget], [StartDate], [Administrator]) VALUES (7, N'Mathematics', 250024.0000, CAST(0x0000999C00000000 AS DateTime), 3) - SET IDENTITY_INSERT [dbo].[Department] OFF - SET IDENTITY_INSERT [dbo].[Person] ON - - INSERT [dbo].[Person] ([PersonID], [LastName], [FirstName], [HireDate], [EnrollmentDate]) VALUES (1, N'Hu', N'Nan', NULL, CAST(0x0000A0BF00000000 AS DateTime)) - INSERT [dbo].[Person] ([PersonID], [LastName], [FirstName], [HireDate], [EnrollmentDate]) VALUES (2, N'Norman', N'Laura', NULL, CAST(0x0000A0BF00000000 AS DateTime)) - INSERT [dbo].[Person] ([PersonID], [LastName], [FirstName], [HireDate], [EnrollmentDate]) VALUES (3, N'Olivotto', N'Nino', NULL, CAST(0x0000A0BF00000000 AS DateTime)) - INSERT [dbo].[Person] ([PersonID], [LastName], [FirstName], [HireDate], [EnrollmentDate]) VALUES (4, N'Anand', N'Arturo', NULL, CAST(0x0000A0BF00000000 AS DateTime)) - INSERT [dbo].[Person] ([PersonID], [LastName], [FirstName], [HireDate], [EnrollmentDate]) VALUES (5, N'Jai', N'Damien', NULL, CAST(0x0000A0BF00000000 AS DateTime)) - INSERT [dbo].[Person] ([PersonID], [LastName], [FirstName], [HireDate], [EnrollmentDate]) VALUES (6, N'Holt', N'Roger', CAST(0x000097F100000000 AS DateTime), NULL) - INSERT [dbo].[Person] ([PersonID], [LastName], [FirstName], [HireDate], [EnrollmentDate]) VALUES (7, N'Martin', N'Randall', CAST(0x00008B1A00000000 AS DateTime), NULL) - SET IDENTITY_INSERT [dbo].[Person] OFF - SET IDENTITY_INSERT [dbo].[StudentGrade] ON - - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (1, N'C1045', 1, CAST(3.50 AS Decimal(3, 2))) - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (2, N'C1045', 2, CAST(3.00 AS Decimal(3, 2))) - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (3, N'C1045', 3, CAST(2.50 AS Decimal(3, 2))) - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (4, N'C1045', 4, CAST(4.00 AS Decimal(3, 2))) - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (5, N'C1045', 5, CAST(3.50 AS Decimal(3, 2))) - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (6, N'C1061', 1, CAST(4.00 AS Decimal(3, 2))) - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (7, N'C1061', 3, CAST(3.50 AS Decimal(3, 2))) - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (8, N'C1061', 4, CAST(2.50 AS Decimal(3, 2))) - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (9, N'C1061', 5, CAST(1.50 AS Decimal(3, 2))) - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (10, N'C2021', 1, CAST(2.50 AS Decimal(3, 2))) - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (11, N'C2021', 2, CAST(3.50 AS Decimal(3, 2))) - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (12, N'C2021', 4, CAST(3.00 AS Decimal(3, 2))) - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (13, N'C2021', 5, CAST(3.00 AS Decimal(3, 2))) - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (14, N'C2042', 1, CAST(2.00 AS Decimal(3, 2))) - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (15, N'C2042', 2, CAST(3.50 AS Decimal(3, 2))) - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (16, N'C2042', 3, CAST(4.00 AS Decimal(3, 2))) - INSERT [dbo].[StudentGrade] ([EnrollmentID], [CourseID], [StudentID], [Grade]) VALUES (17, N'C2042', 5, CAST(3.00 AS Decimal(3, 2))) - SET IDENTITY_INSERT [dbo].[StudentGrade] OFF - ALTER TABLE [dbo].[Course] WITH CHECK ADD CONSTRAINT [FK_Course_Department] FOREIGN KEY([DepartmentID]) - REFERENCES [dbo].[Department] ([DepartmentID]) - GO - ALTER TABLE [dbo].[Course] CHECK CONSTRAINT [FK_Course_Department] - GO - ALTER TABLE [dbo].[StudentGrade] WITH CHECK ADD CONSTRAINT [FK_StudentGrade_Student] FOREIGN KEY([StudentID]) - REFERENCES [dbo].[Person] ([PersonID]) - GO - ALTER TABLE [dbo].[StudentGrade] CHECK CONSTRAINT [FK_StudentGrade_Student] - GO - - - Next, compile and execute the following: - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - using System.Threading.Tasks; - - class Program - { - static class SqlHelper - { - // Set the connection, command, and then execute the command with non query. - public static Int32 ExecuteNonQuery(String connectionString, String commandText, - CommandType commandType, params SqlParameter[] parameters) - { - using (SqlConnection conn = new SqlConnection(connectionString)) - { - using (SqlCommand cmd = new SqlCommand(commandText, conn)) - { - // There are three command types: StoredProcedure, Text, TableDirect. The TableDirect - // type is only for OLE DB. - cmd.CommandType = commandType; - cmd.Parameters.AddRange(parameters); - - conn.Open(); - return cmd.ExecuteNonQuery(); - } - } - } - - // Set the connection, command, and then execute the command and only return one value. - public static Object ExecuteScalar(String connectionString, String commandText, - CommandType commandType, params SqlParameter[] parameters) - { - using (SqlConnection conn = new SqlConnection(connectionString)) - { - using (SqlCommand cmd = new SqlCommand(commandText, conn)) - { - cmd.CommandType = commandType; - cmd.Parameters.AddRange(parameters); - - conn.Open(); - return cmd.ExecuteScalar(); - } - } - } - - // Set the connection, command, and then execute the command with query and return the reader. - public static SqlDataReader ExecuteReader(String connectionString, String commandText, - CommandType commandType, params SqlParameter[] parameters) - { - SqlConnection conn = new SqlConnection(connectionString); - - using (SqlCommand cmd = new SqlCommand(commandText, conn)) - { - cmd.CommandType = commandType; - cmd.Parameters.AddRange(parameters); - - conn.Open(); - // When using CommandBehavior.CloseConnection, the connection will be closed when the - // IDataReader is closed. - SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection); - - return reader; - } - } - } - - static void Main(string[] args) - { - String connectionString = "Data Source=(local);Initial Catalog=MySchool;Integrated Security=True;"; - - CountCourses(connectionString, 2012); - Console.WriteLine(); - - Console.WriteLine("Following result is the departments that started from 2007:"); - GetDepartments(connectionString, 2007); - Console.WriteLine(); - - Console.WriteLine("Add the credits when the credits of course are lower than 4."); - AddCredits(connectionString, 4); - Console.WriteLine(); - - Console.WriteLine("Please press any key to exit..."); - Console.ReadKey(); - } - - static void CountCourses(String connectionString, Int32 year) - { - String commandText = "Select Count([CourseID]) FROM [MySchool].[dbo].[Course] Where Year=@Year"; - SqlParameter parameterYear = new SqlParameter("@Year", SqlDbType.Int); - parameterYear.Value = year; - - Object oValue = SqlHelper.ExecuteScalar(connectionString, commandText, CommandType.Text, parameterYear); - Int32 count; - if (Int32.TryParse(oValue.ToString(), out count)) - { - Console.WriteLine("There {0} {1} course{2} in {3}.", count > 1 ? "are" : "is", count, count > 1 ? "s" : null, year); - } - } - - // Display the Departments that start from the specified year. - static void GetDepartments(String connectionString, Int32 year) - { - String commandText = "dbo.GetDepartmentsOfSpecifiedYear"; - - // Specify the year of StartDate - SqlParameter parameterYear = new SqlParameter("@Year", SqlDbType.Int); - parameterYear.Value = year; - - // When the direction of parameter is set as Output, you can get the value after - // executing the command. - SqlParameter parameterBudget = new SqlParameter("@BudgetSum", SqlDbType.Money); - parameterBudget.Direction = ParameterDirection.Output; - - using (SqlDataReader reader = SqlHelper.ExecuteReader(connectionString, commandText, - CommandType.StoredProcedure, parameterYear, parameterBudget)) - { - Console.WriteLine("{0,-20}{1,-20}{2,-20}{3,-20}", "Name", "Budget", "StartDate", - "Administrator"); - while (reader.Read()) - { - Console.WriteLine("{0,-20}{1,-20:C}{2,-20:d}{3,-20}", reader["Name"], - reader["Budget"], reader["StartDate"], reader["Administrator"]); - } - } - Console.WriteLine("{0,-20}{1,-20:C}", "Sum:", parameterBudget.Value); - } - - // If credits of course is lower than the certain value, the method will add the credits. - static void AddCredits(String connectionString, Int32 creditsLow) - { - String commandText = "Update [MySchool].[dbo].[Course] Set Credits=Credits+1 Where Credits<@Credits"; - - SqlParameter parameterCredits = new SqlParameter("@Credits", creditsLow); - - Int32 rows = SqlHelper.ExecuteNonQuery(connectionString, commandText, CommandType.Text, parameterCredits); - - Console.WriteLine("{0} row{1} {2} updated.", rows, rows > 1 ? "s" : null, rows > 1 ? "are" : "is"); - } - } - - + features the following methods for executing commands at a SQL Server database: + +|Item|Description| +|----------|-----------------| +||Initiates the asynchronous execution of the Transact-SQL statement or stored procedure that is described by this , generally executing commands such as INSERT, DELETE, UPDATE, and SET statements. Each call to must be paired with a call to which finishes the operation, typically on a separate thread.| +||Initiates the asynchronous execution of the Transact-SQL statement or stored procedure that is described by this and retrieves one or more results sets from the server. Each call to must be paired with a call to which finishes the operation, typically on a separate thread.| +||Initiates the asynchronous execution of the Transact-SQL statement or stored procedure that is described by this . Each call to `BeginExecuteXmlReader` must be paired with a call to `EndExecuteXmlReader`, which finishes the operation, typically on a separate thread, and returns an object.| +||Executes commands that return rows. For increased performance, invokes commands using the Transact-SQL `sp_executesql` system stored procedure. Therefore, might not have the effect that you want if used to execute commands such as Transact-SQL SET statements.| +||Executes commands such as Transact-SQL INSERT, DELETE, UPDATE, and SET statements.| +||Retrieves a single value (for example, an aggregate value) from a database.| +||Sends the to the and builds an object.| + +You can reset the property and reuse the object. However, you must close the before you can execute a new or previous command. + +If a is generated by the method executing a , the remains open when the severity level is 19 or less. When the severity level is 20 or greater, the server ordinarily closes the . However, the user can reopen the connection and continue. + +> [!NOTE] +> Nameless, also called ordinal, parameters are not supported by the .NET Framework Data Provider for SQL Server. + +## Examples +The following example creates a , a , and a . The example reads through the data, writing it to the console. Finally, the example closes the and then the as it exits the `Using` code blocks. + +[!code-csharp[SqlCommand Example#1](~/../sqlclient/doc/samples/SqlCommand.cs#1)] + +The following sample shows how to create and execute different types of SqlCommand objects. + +First you must create the sample database, by executing the following script: + +[!code-sql[Setup Database](~/../sqlclient/doc/samples/SqlCommand_Setup.sql#1)] + +Next, compile and execute the following: + +[!code-csharp[SqlCommand Example#2](~/../sqlclient/doc/samples/SqlCommand_Intro.cs#1)] +]]> + @@ -739,7 +336,7 @@ - The method starts the process of asynchronously executing a Transact-SQL statement or stored procedure that does not return rows, so that other tasks can run concurrently while the statement is executing. When the statement has completed, developers must call the method to finish the operation. The method returns immediately, but until the code executes the corresponding method call, it must not execute any other calls that start a synchronous or asynchronous execution against the same object. Calling the before the command's execution is completed causes the object to block until the execution is finished. + The method starts the process of asynchronously executing a Transact-SQL statement or stored procedure that does not return rows, so that other tasks can run concurrently while the statement is executing. When the statement has completed, developers must call the method to finish the operation. The method returns immediately, but until the code executes the corresponding method call, it must not execute any other calls that start a synchronous or asynchronous execution against the same object. Calling the before the command's execution is completed causes the object to block until the execution is finished. Note that the command text and parameters are sent to the server synchronously. If a large command or many parameters are sent, this method may block during writes. After the command is sent, the method returns immediately without waiting for an answer from the server - that is, reads are asynchronous. @@ -839,35 +436,35 @@ - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + - + Any error that occurred while executing the command text. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + The closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - - + + is set to true and a parameter with direction Output or InputOutput has been added to the collection. - + @@ -892,10 +489,10 @@ - The method starts the process of asynchronously executing a Transact-SQL statement or stored procedure that does not return rows, so that other tasks can run concurrently while the statement is executing. When the statement has completed, developers must call the method to finish the operation. The method returns immediately, but until the code executes the corresponding method call, it must not execute any other calls that start a synchronous or asynchronous execution against the same object. Calling the before the command's execution is completed causes the object to block until the execution is finished. + The method starts the process of asynchronously executing a Transact-SQL statement or stored procedure that does not return rows, so that other tasks can run concurrently while the statement is executing. When the statement has completed, developers must call the method to finish the operation. The method returns immediately, but until the code executes the corresponding method call, it must not execute any other calls that start a synchronous or asynchronous execution against the same object. Calling the before the command's execution is completed causes the object to block until the execution is finished. - The parameter lets you specify an delegate that is called when the statement has completed. You can call the method from within this delegate procedure, or from any other location within your application. In addition, you can pass any object in the parameter, and your callback procedure can retrieve this information using the property. + The parameter lets you specify an delegate that is called when the statement has completed. You can call the method from within this delegate procedure, or from any other location within your application. In addition, you can pass any object in the parameter, and your callback procedure can retrieve this information using the property. Note that the command text and parameters are sent to the server synchronously. If a large command or many parameters are sent, this method may block during writes. After the command is sent, the method returns immediately without waiting for an answer from the server--that is, reads are asynchronous. @@ -1112,35 +709,35 @@ - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + - + Any error that occurred while executing the command text. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + The closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - - + + is set to true and a parameter with direction Output or InputOutput has been added to the collection. - + @@ -1153,7 +750,7 @@ - The method starts the process of asynchronously executing a Transact-SQL statement or stored procedure that returns rows, so that other tasks can run concurrently while the statement is executing. When the statement has completed, developers must call the method to finish the operation and retrieve the returned by the command. The method returns immediately, but until the code executes the corresponding method call, it must not execute any other calls that start a synchronous or asynchronous execution against the same object. Calling the before the command's execution is completed causes the object to block until the execution is finished. + The method starts the process of asynchronously executing a Transact-SQL statement or stored procedure that returns rows, so that other tasks can run concurrently while the statement is executing. When the statement has completed, developers must call the method to finish the operation and retrieve the returned by the command. The method returns immediately, but until the code executes the corresponding method call, it must not execute any other calls that start a synchronous or asynchronous execution against the same object. Calling the before the command's execution is completed causes the object to block until the execution is finished. Note that the command text and parameters are sent to the server synchronously. If a large command or many parameters are sent, this method may block during writes. After the command is sent, the method returns immediately without waiting for an answer from the server - that is, reads are asynchronous. Although command execution is asynchronous, value fetching is still synchronous. This means that calls to may block if more data is required and the underlying network's read operation blocks. @@ -1276,35 +873,35 @@ - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + - + Any error that occurred while executing the command text. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + The closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - - + + is set to true and a parameter with direction Output or InputOutput has been added to the collection. - + @@ -1326,7 +923,7 @@ - The method starts the process of asynchronously executing a Transact-SQL statement or stored procedure that returns rows, so that other tasks can run concurrently while the statement is executing. When the statement has completed, developers must call the method to finish the operation and retrieve the returned by the command. The method returns immediately, but until the code executes the corresponding method call, it must not execute any other calls that start a synchronous or asynchronous execution against the same object. Calling the before the command's execution is completed causes the object to block until the execution is finished. + The method starts the process of asynchronously executing a Transact-SQL statement or stored procedure that returns rows, so that other tasks can run concurrently while the statement is executing. When the statement has completed, developers must call the method to finish the operation and retrieve the returned by the command. The method returns immediately, but until the code executes the corresponding method call, it must not execute any other calls that start a synchronous or asynchronous execution against the same object. Calling the before the command's execution is completed causes the object to block until the execution is finished. The behavior parameter lets you specify options that control the behavior of the command and its connection. These values can be combined (using the programming language's OR operator); generally, developers use the CommandBehavior.CloseConnection value to make sure that the connection is closed by the runtime when the is closed. @@ -1452,35 +1049,35 @@ - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + - + Any error that occurred while executing the command text. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + The closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - - + + is set to true and a parameter with direction Output or InputOutput has been added to the collection. - + @@ -1505,10 +1102,10 @@ - The method starts the process of asynchronously executing a Transact-SQL statement or stored procedure that returns rows, so that other tasks can run concurrently while the statement is executing. When the statement has completed, developers must call the method to finish the operation and retrieve the returned by the command. The method returns immediately, but until the code executes the corresponding method call, it must not execute any other calls that start a synchronous or asynchronous execution against the same object. Calling the before the command's execution is completed cause the object to block until the execution is finished. + The method starts the process of asynchronously executing a Transact-SQL statement or stored procedure that returns rows, so that other tasks can run concurrently while the statement is executing. When the statement has completed, developers must call the method to finish the operation and retrieve the returned by the command. The method returns immediately, but until the code executes the corresponding method call, it must not execute any other calls that start a synchronous or asynchronous execution against the same object. Calling the before the command's execution is completed cause the object to block until the execution is finished. - The parameter lets you specify an delegate that is called when the statement has completed. You can call the method from within this delegate procedure, or from any other location within your application. In addition, you can pass any object in the parameter, and your callback procedure can retrieve this information using the property. + The parameter lets you specify an delegate that is called when the statement has completed. You can call the method from within this delegate procedure, or from any other location within your application. In addition, you can pass any object in the parameter, and your callback procedure can retrieve this information using the property. Note that the command text and parameters are sent to the server synchronously. If a large command or many parameters are sent, this method may block during writes. After the command is sent, the method returns immediately without waiting for an answer from the server--that is, reads are asynchronous. Although command execution is asynchronous, value fetching is still synchronous. This means that calls to may block if more data is required and the underlying network's read operation blocks. @@ -1528,7 +1125,7 @@ - The following Windows application demonstrates the use of the method, executing a Transact-SQL statement that includes a delay of a few seconds (emulating a long-running command). Because the sample executes the command asynchronously, the form remains responsive while awaiting the results. This example passes the executing object as the parameter; doing so makes it simple to retrieve the object from within the callback procedure, so that the code can call the method corresponding to the initial call to . + The following Windows application demonstrates the use of the method, executing a Transact-SQL statement that includes a delay of a few seconds (emulating a long-running command). Because the sample executes the command asynchronously, the form remains responsive while awaiting the results. This example passes the executing object as the parameter; doing so makes it simple to retrieve the object from within the callback procedure, so that the code can call the method corresponding to the initial call to . This example demonstrates many important techniques. This includes calling a method that interacts with the form from a separate thread. In addition, this example demonstrates how you must block users from executing a command multiple times concurrently, and how you must make sure that the form does not close before the callback procedure is called. @@ -1637,33 +1234,33 @@ - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + - Any error that occurred while executing the command text. - + Any error that occurred while executing the command text. + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + The closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - - + + is set to true and a parameter with direction Output or InputOutput has been added to the collection. - + @@ -1691,10 +1288,10 @@ - The method starts the process of asynchronously executing a Transact-SQL statement or stored procedure that returns rows, so that other tasks can run concurrently while the statement is executing. When the statement has completed, developers must call the method to finish the operation and retrieve the returned by the command. The method returns immediately, but until the code executes the corresponding method call, it must not execute any other calls that start a synchronous or asynchronous execution against the same object. Calling the before the command's execution is completed causes the object to block until the execution is finished. + The method starts the process of asynchronously executing a Transact-SQL statement or stored procedure that returns rows, so that other tasks can run concurrently while the statement is executing. When the statement has completed, developers must call the method to finish the operation and retrieve the returned by the command. The method returns immediately, but until the code executes the corresponding method call, it must not execute any other calls that start a synchronous or asynchronous execution against the same object. Calling the before the command's execution is completed causes the object to block until the execution is finished. - The parameter lets you specify an delegate that is called when the statement has completed. You can call the method from within this delegate procedure, or from any other location within your application. In addition, you can pass any object in the stateObject parameter, and your callback procedure can retrieve this information using the property. + The parameter lets you specify an delegate that is called when the statement has completed. You can call the method from within this delegate procedure, or from any other location within your application. In addition, you can pass any object in the stateObject parameter, and your callback procedure can retrieve this information using the property. The parameter lets you specify options that control the behavior of the command and its connection. These values can be combined together (using the programming language's Or operator); generally, developers use the CloseConnection value to make sure that the connection is closed by the runtime when the is closed. Developers can also optimize the behavior of the by specifying the SingleRow value when it is known in advance that the Transact-SQL statement or stored procedure only returns a single row. @@ -1717,7 +1314,7 @@ - The following Windows application demonstrates the use of the method, executing a Transact-SQL statement that includes a delay of a few seconds (emulating a long-running command). Because the sample executes the command asynchronously, the form remains responsive while awaiting the results. This example passes the executing object as the stateObject parameter; doing so makes it simple to retrieve the object from within the callback procedure, so that the code can call the method corresponding to the initial call to . + The following Windows application demonstrates the use of the method, executing a Transact-SQL statement that includes a delay of a few seconds (emulating a long-running command). Because the sample executes the command asynchronously, the form remains responsive while awaiting the results. This example passes the executing object as the stateObject parameter; doing so makes it simple to retrieve the object from within the callback procedure, so that the code can call the method corresponding to the initial call to . This example demonstrates many important techniques. This includes calling a method that interacts with the form from a separate thread. In addition, this example demonstrates how you must block users from executing a command multiple times concurrently, and how you must make sure that the form does not close before the callback procedure is called. @@ -1919,35 +1516,35 @@ - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + - + Any error that occurred while executing the command text. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + The closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - - + + is set to true and a parameter with direction Output or InputOutput has been added to the collection. - + @@ -1966,7 +1563,7 @@ - The method starts the process of asynchronously executing a Transact-SQL statement that returns rows as XML, so that other tasks can run concurrently while the statement is executing. When the statement has completed, developers must call the method to finish the operation and retrieve the XML returned by the command. The method returns immediately, but until the code executes the corresponding method call, it must not execute any other calls that start a synchronous or asynchronous execution against the same object. Calling the EndExecuteXmlReader before the command's execution is completed causes the object to block until the execution is finished. + The method starts the process of asynchronously executing a Transact-SQL statement that returns rows as XML, so that other tasks can run concurrently while the statement is executing. When the statement has completed, developers must call the method to finish the operation and retrieve the XML returned by the command. The method returns immediately, but until the code executes the corresponding method call, it must not execute any other calls that start a synchronous or asynchronous execution against the same object. Calling the EndExecuteXmlReader before the command's execution is completed causes the object to block until the execution is finished. The property ordinarily specifies a Transact-SQL statement with a valid FOR XML clause. However, CommandText can also specify a statement that returns ntext data that contains valid XML. @@ -1980,7 +1577,7 @@ - This method can also be used to retrieve a single-row, single-column result set. In this case, if more than one row is returned, the method attaches the to the value on the first row, and discards the rest of the result set. + This method can also be used to retrieve a single-row, single-column result set. In this case, if more than one row is returned, the method attaches the to the value on the first row, and discards the rest of the result set. The multiple active result set (MARS) feature lets multiple actions use the same connection. @@ -2086,35 +1683,35 @@ - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + - + Any error that occurred while executing the command text. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + The closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - - + + is set to true and a parameter with direction Output or InputOutput has been added to the collection. - + @@ -2139,10 +1736,10 @@ - The method starts the process of asynchronously executing a Transact-SQL statement or stored procedure that returns rows as XML, so that other tasks can run concurrently while the statement is executing. When the statement has completed, developers must call the method to finish the operation and retrieve the requested XML data. The method returns immediately, but until the code executes the corresponding method call, it must not execute any other calls that start a synchronous or asynchronous execution against the same object. Calling the before the command's execution is completed causes the object to block until the execution is finished. + The method starts the process of asynchronously executing a Transact-SQL statement or stored procedure that returns rows as XML, so that other tasks can run concurrently while the statement is executing. When the statement has completed, developers must call the method to finish the operation and retrieve the requested XML data. The method returns immediately, but until the code executes the corresponding method call, it must not execute any other calls that start a synchronous or asynchronous execution against the same object. Calling the before the command's execution is completed causes the object to block until the execution is finished. - The property ordinarily specifies a Transact-SQL statement with a valid FOR XML clause. However, can also specify a statement that returns data that contains valid XML. This method can also be used to retrieve a single-row, single-column result set. In this case, if more than one row is returned, the method attaches the to the value on the first row, and discards the rest of the result set. + The property ordinarily specifies a Transact-SQL statement with a valid FOR XML clause. However, can also specify a statement that returns data that contains valid XML. This method can also be used to retrieve a single-row, single-column result set. In this case, if more than one row is returned, the method attaches the to the value on the first row, and discards the rest of the result set. A typical query can be formatted as in the following C# example: @@ -2153,13 +1750,13 @@ - This method can also be used to retrieve a single-row, single-column result set. In this case, if more than one row is returned, the method attaches the to the value on the first row, and discards the rest of the result set. + This method can also be used to retrieve a single-row, single-column result set. In this case, if more than one row is returned, the method attaches the to the value on the first row, and discards the rest of the result set. The multiple active result set (MARS) feature lets multiple actions use the same connection. - The parameter lets you specify an delegate that is called when the statement has completed. You can call the method from within this delegate procedure, or from any other location within your application. In addition, you can pass any object in the stateObject parameter, and your callback procedure can retrieve this information using the property. + The parameter lets you specify an delegate that is called when the statement has completed. You can call the method from within this delegate procedure, or from any other location within your application. In addition, you can pass any object in the stateObject parameter, and your callback procedure can retrieve this information using the property. Note that the command text and parameters are sent to the server synchronously. If a large command or many parameters is sent, this method may block during writes. After the command is sent, the method returns immediately without waiting for an answer from the server--that is, reads are asynchronous. @@ -2176,7 +1773,7 @@ - The following Windows application demonstrates the use of the method, executing a Transact-SQL statement that includes a delay of a few seconds (emulating a long-running command). This example passes the executing object as the parameter--doing so makes it simple to retrieve the object from within the callback procedure, so that the code can call the method corresponding to the initial call to . + The following Windows application demonstrates the use of the method, executing a Transact-SQL statement that includes a delay of a few seconds (emulating a long-running command). This example passes the executing object as the parameter--doing so makes it simple to retrieve the object from within the callback procedure, so that the code can call the method corresponding to the initial call to . To set up this example, create a new Windows application. Put a control, a control, and a control on the form (accepting the default name for each control). Add the following code to the form's class, modifying the connection string as needed for your environment. @@ -2373,35 +1970,35 @@ - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + - + Any error that occurred while executing the command text. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + The closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - - + + is set to true and a parameter with direction Output or InputOutput has been added to the collection. - + @@ -2576,57 +2173,21 @@ The time in seconds to wait for the command to execute. The default is 30 seconds. - - A value of 0 indicates no limit (an attempt to execute a command will wait indefinitely). - - - - The property will be ignored during old-style asynchronous method calls such as . It will be honored by the newer async methods such as . - - - - - This property is the cumulative time-out (for all network packets that are read during the invocation of a method) for all network reads during command execution or processing of the results. A time-out can still occur after the first row is returned, and does not include user processing time, only network read time. - - - - For example, with a 30 second time out, if requires two network packets, then it has 30 seconds to read both network packets. If you call again, it will have another 30 seconds to read any data that it requires. - - - - - - using System; - using Microsoft.Data.SqlClient; + [!NOTE] +> The property will be ignored during old-style asynchronous method calls such as . It will be honored by the newer async methods such as . - // Wait for 5 second delay in the command - string queryString = "waitfor delay '00:00:05'"; - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - SqlCommand command = new SqlCommand(queryString, connection); - // Setting command timeout to 1 second - command.CommandTimeout = 1; - try - { - command.ExecuteNonQuery(); - } - catch (SqlException e) - { - Console.WriteLine("Got expected SqlException due to command timeout "); - Console.WriteLine(e); - } - } - } - } - - +> [!NOTE] +> This property is the cumulative time-out (for all network packets that are read during the invocation of a method) for all network reads during command execution or processing of the results. A time-out can still occur after the first row is returned, and does not include user processing time, only network read time. + +For example, with a 30 second time out, if requires two network packets, then it has 30 seconds to read both network packets. If you call again, it will have another 30 seconds to read any data that it requires. + +[!code-csharp[SqlCommand CommandTimeout](~/../sqlclient/doc/samples/SqlCommand_CommandTimeout.cs)] +]]> + The value set is less than 0. @@ -2825,23 +2386,21 @@ A value indicating whether the command object should optimize parameter performance by disabling Output and InputOutput parameter directions when submitting the command to the SQL Server. The default is . - - You must set the value for this property before the command is executed for it to take effect. - - - When a command is submitted to the server with parameters a list of the parameter names is sent as part of the submission. The list is used on the server to match Output and InputOutput parameters to the results of the query execution so that the values can be returned to the caller. This option disables the construction and submission of the parameter name list and as a consequence disables the use of Output and InputOutput parameters. The return parameter is not affected by this option. - - - A command sent with this option changes the way parameters are handled on the server, because there is no need to maintain an output parameter map. The result of this change is that queries with large numbers of input parameters may execute much faster. - - - The fewest number of parameters where this will take effect depends on the individual situation and should be detected by measuring query duration with and without the option enabled. Any query with more than 24 parameters may show lower overall query duration. Queries with parameter counts lower than 24 are unlikely to show a difference. - - - - If the option is enabled and a parameter with Direction Output or InputOutput is present in the Parameters collection an InvalidOperationException will be thrown when the command is executed. - - + [!NOTE] +If the option is enabled and a parameter with Direction Output or InputOutput is present in the Parameters collection an InvalidOperationException will be thrown when the command is executed. + +]]> + @@ -2868,12 +2427,12 @@ - + The amount of time specified in elapsed and the asynchronous operation specified with is not complete. - - + + In some situations, can set incorrectly. If this occurs and is called, EndExecuteNonQuery could raise a SqlException error if the amount of time specified in elapsed and the asynchronous operation specified with is not complete. To correct this situation, you should either increase the value of CommandTimeout or reduce the work being done by the asynchronous operation. - + @@ -3010,25 +2569,25 @@ - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + - + An exception occurred while executing the command against a locked row. This exception is not generated when you are using Microsoft .NET Framework version 1.0. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + @@ -3052,44 +2611,46 @@ A task representing the asynchronous operation. - - For more information about asynchronous programming in the .NET Framework Data Provider for SQL Server, see Asynchronous Programming. - - - For long-running queries on the server, consider using due to a known issue with canceling queries via a cancellation token. Also, consider canceling execution using the method. - + [!NOTE] +> For long running queries on the server, consider using due to a known issue with canceling queries via a cancellation token. Also, consider canceling execution using the method. + +]]> - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + - + Calling more than once for the same instance before task completion. - - + + The closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + SQL Server returned an error while executing the command text. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + @@ -3107,87 +2668,54 @@ A object. - - When the property is set to , the property should be set to the name of the stored procedure. The command executes this stored procedure when you call . - - - If a transaction is deadlocked, an exception may not be thrown until is called. - - - The multiple active result set (MARS) feature allows for multiple actions using the same connection. - - - If you use or to access XML data, SQL Server will return any XML results greater than 2,033 characters in length in multiple rows of 2,033 characters each. To avoid this behavior, use or to read FOR XML queries. - + property is set to `StoredProcedure`, the property should be set to the name of the stored procedure. The command executes this stored procedure when you call . + +> [!NOTE] +> If a transaction is deadlocked, an exception may not be thrown until is called. + +The multiple active result set (MARS) feature allows for multiple actions using the same connection. + +If you use or to access XML data, SQL Server will return any XML results greater than 2,033 characters in length in multiple rows of 2,033 characters each. To avoid this behavior, use or to read FOR XML queries. + +## Examples +The following example creates a , and then executes it by passing a string that is a Transact-SQL SELECT statement, and a string to use to connect to the data source. + +[!code-csharp[SqlCommand_ExecuteReader](~/../sqlclient/doc/samples/SqlCommand_ExecuteReader.cs)] +]]> - - - The following example creates a , and then executes it by passing a string that is a Transact-SQL SELECT statement, and a string to use to connect to the data source. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string str = "Data Source=(local);Initial Catalog=Northwind;" - + "Integrated Security=SSPI"; - string qs = "SELECT OrderID, CustomerID FROM dbo.Orders;"; - CreateCommand(qs, str); - } - - private static void CreateCommand(string queryString, string connectionString) - { - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - SqlCommand command = new SqlCommand(queryString, connection); - SqlDataReader reader = command.ExecuteReader(); - while (reader.Read()) - { - Console.WriteLine(String.Format("{0}", reader[0])); - } - } - } - } - - - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + - + An exception occurred while executing the command against a locked row. This exception is not generated when you are using Microsoft .NET Framework version 1.0. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + The current state of the connection is closed. requires an open . - - + + The closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + @@ -3208,18 +2736,22 @@ A object. - - When the property is set to , the property should be set to the name of the stored procedure. The command executes this stored procedure when you call . - - - Use to retrieve large values and binary data. Otherwise, an might occur and the connection will be closed. - - - The multiple active result set (MARS) feature allows for multiple actions using the same connection. - - - If you use or to access XML data, SQL Server will return any XML results greater than 2,033 characters in length in multiple rows of 2,033 characters each. To avoid this behavior, use or to read FOR XML queries. - + property is set to `StoredProcedure`, the property should be set to the name of the stored procedure. The command executes this stored procedure when you call . + +> [!NOTE] +> Use to retrieve large values and binary data. Otherwise, an might occur and the connection will be closed. + +The multiple active result set (MARS) feature allows for multiple actions using the same connection. + +If you use or to access XML data, SQL Server will return any XML results greater than 2,033 characters in length in multiple rows of 2,033 characters each. To avoid this behavior, use or to read FOR XML queries. + +## Examples +The following example creates a , and then executes it by passing a string that is a Transact-SQL SELECT statement, and a string to use to connect to the data source. is set to . + +[!code-csharp[SqlCommand_ExecuteReader2](~/../sqlclient/doc/samples/SqlCommand_ExecuteReader2.cs#1)] +]]> @@ -3259,15 +2791,15 @@ - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + @@ -3291,24 +2823,26 @@ A task representing the asynchronous operation. - - For more information about asynchronous programming in the .NET Framework Data Provider for SQL Server, see Asynchronous Programming. - - - For long-running queries on the server, consider using due to a known issue with canceling queries via a cancellation token. Also, consider canceling execution using the method. - + [!NOTE] +> For long running queries on the server, consider using due to a known issue with canceling queries via a cancellation token. Also, consider canceling execution using the method. + +]]> - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + @@ -3316,22 +2850,22 @@ - + Calling more than once for the same instance before task completion. - - + + The closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + SQL Server returned an error while executing the command text. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + @@ -3356,15 +2890,15 @@ - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + @@ -3372,22 +2906,22 @@ - + Calling more than once for the same instance before task completion. - - + + closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + SQL Server returned an error while executing the command text. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + @@ -3412,15 +2946,15 @@ - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + @@ -3428,22 +2962,22 @@ - + Calling more than once for the same instance before task completion. - - + + The closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + SQL Server returned an error while executing the command text. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + @@ -3471,15 +3005,15 @@ - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + @@ -3487,22 +3021,22 @@ - + Calling more than once for the same instance before task completion. - - + + The closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + SQL Server returned an error while executing the command text. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + @@ -3557,25 +3091,25 @@ - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + - + An exception occurred while executing the command against a locked row. This exception is not generated when you are using Microsoft .NET Framework version 1.0. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + @@ -3599,44 +3133,46 @@ A task representing the asynchronous operation. - - For more information about asynchronous programming in the .NET Framework Data Provider for SQL Server, see Asynchronous Programming. - - - For long-running queries on the server, consider using due to a known issue with canceling queries via a cancellation token. Also, consider canceling execution using the method. - + [!NOTE] +> For long running queries on the server, consider using due to a known issue with canceling queries via a cancellation token. Also, consider canceling execution using the method. + +]]> - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + - + Calling more than once for the same instance before task completion. - - + + The closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + SQL Server returned an error while executing the command text. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + @@ -3696,25 +3232,25 @@ - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + - + An exception occurred while executing the command against a locked row. This exception is not generated when you are using Microsoft .NET Framework version 1.0. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + @@ -3736,44 +3272,47 @@ A task representing the asynchronous operation. - - The returned by this method does not support asynchronous operations. For more information about asynchronous programming in the .NET Framework Data Provider for SQL Server, see Asynchronous Programming. - - - For long-running queries on the server, consider using due to a known issue with canceling queries via a cancellation token. Also, consider canceling execution using the method. - + [!NOTE] +> For long running queries on the server, consider using due to a known issue with canceling queries via a cancellation token. Also, consider canceling execution using the method. + +]]> - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + - + Calling more than once for the same instance before task completion. - - + + The closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + SQL Server returned an error while executing the command text. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + @@ -3798,35 +3337,35 @@ - + A other than Binary or VarBinary was used when was set to . For more information about streaming, see SqlClient Streaming Support. - - + + A other than Char, NChar, NVarChar, VarChar, or Xml was used when was set to . - - + + A other than Xml was used when was set to . - + - + Calling more than once for the same instance before task completion. - - + + The closed or dropped during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + - + SQL Server returned an error while executing the command text. - - + + A timeout occurred during a streaming operation. For more information about streaming, see SqlClient Streaming Support. - + @@ -3859,9 +3398,9 @@ Custom master key store providers can be registered with the driver at three different layers. The precedence of the three registrations is as follows: - The per-command registration will be checked if it is not empty. - If the per-command registration is empty, the per-connection registration will be checked if it is not empty. - If the per-connection registration is empty, the global registration will be checked. + The per-command registration will be checked if it is not empty. + If the per-command registration is empty, the per-connection registration will be checked if it is not empty. + If the per-connection registration is empty, the global registration will be checked. Once any key store provider is found at a registration level, the driver will NOT fall back to the other registrations to search for a provider. If providers are registered but the proper provider is not found at a level, an exception will be thrown containing only the registered providers in the registration that was checked. @@ -3875,11 +3414,11 @@ - A null dictionary was provided. - A string key in the dictionary was null or empty. - + A null dictionary was provided. + A string key in the dictionary was null or empty. + A value in the dictionary was null. - + @@ -3894,320 +3433,58 @@ When set to null (default), the default non-retryable provider will be used. - - You must set the value for this property before the command is executed for it to take effect. - - - To apply the retry logic, do the following steps before executing the command: - - - - Define the configuration parameters by using type. - - - Create a by using one of the following static methods of the class: - - - - - - - - - - - - - - - - - Assign the object to the RetryLogicProvider property. - - - - Detecting retryable exceptions is a vital part of the retry pattern. Before applying retry logic, it is important to investigate exceptions and choose a retry provider that best fits your scenario. First, log your exceptions and find transient faults. - - - The command timeout restarts for each execution of a command within the retry logic and after applying the retry time delay. There is no timing overlap between these two actions. - - - The default retry logic provider is not enabled unless it is configured in an application configuration file. For more information, see Configurable retry logic configuration file. - - - A command with isn't compatible with the built-in retry logic. The underlying connection is immediately closed after the first execution attempt and is no longer available for subsequent retries. - + type. +2. Create a by using one of the following static methods of the class: + - + - + - + - +3. Assign the object to the `RetryLogicProvider` property. + +> [!NOTE] +> Detecting retriable exceptions is a vital part of the retry pattern. Before applying retry logic, it is important to investigate exceptions and choose a retry provider that best fits your scenario. First, log your exceptions and find transient faults. + +> [!NOTE] +> The command **timeout** restarts for each execution of a command within the retry logic and after applying the retry time delay. There is no timing overlap between these two actions. + +> [!NOTE] +> The default retry logic provider is not enabled unless it is configured in an application configuration file. For more information, see [Configurable retry logic configuration file](https://learn.microsoft.com/sql/connect/ado-net/configurable-retry-logic-config-file-sqlclient). + +> [!CAUTION] +> A command with isn't compatible with the built-in retry logic. The underlying connection is immediately closed after the first execution attempt and is no longer available for subsequent retries. + +## Example +The following sample creates a database and establishes an active connection to it. While the database has an active connection, it tries to drop it with a new and a that uses a . You should kill the active connection through the database to unblock the second command before exceeding the number of retries. +The blocking connection simulates a situation like a command still running in the database and unlikely to finish. + +[!code-csharp[SqlConfigurableRetryLogic_SqlCommand#1](~/../sqlclient/doc/samples/SqlConfigurableRetryLogic_SqlCommand.cs#1)] + +### How to use with synchronous commands + +[!code-csharp[SqlConfigurableRetryLogic_SqlCommand#2](~/../sqlclient/doc/samples/SqlConfigurableRetryLogic_SqlCommand.cs#2)] + +### How to use with asynchoronous commands + +[!code-csharp[SqlConfigurableRetryLogic_SqlCommand#3](~/../sqlclient/doc/samples/SqlConfigurableRetryLogic_SqlCommand.cs#3)] + +### How to use with legacy asynchronous commands +Besides assigning the provider to the command and executing the command, it's possible to run it directly using the following methods: +- +- +- + +[!code-csharp[SqlConfigurableRetryLogic_SqlCommand#4](~/../sqlclient/doc/samples/SqlConfigurableRetryLogic_SqlCommand.cs#4)] + +> [!NOTE] +> The Asynchronous Programming Model (APM) is a legacy pattern that uses a pair of methods starting with `Begin` and `End`, and an interface called `IAsyncResult`. It's not recommended to use this pattern in new applications. These methods are for backwards compatibility. + +]]> - - - The following sample creates a database and establishes an active connection to it. While the database has an active connection, it tries to drop it with a new and a that uses a . You should kill the active connection through the database to unblock the second command before exceeding the number of retries. The blocking connection simulates a situation like a command still running in the database and unlikely to finish. - - - - /// Detecting retriable exceptions is a vital part of the retry pattern. - /// Before applying retry logic it is important to investigate exceptions and choose a retry provider that best fits your scenario. - /// First, log your exceptions and find transient faults. - /// The purpose of this sample is to illustrate how to use this feature and the condition might not be realistic. - - private const string DefaultDB = "Northwind"; - private const string CnnStringFormat = "Server=localhost; Initial Catalog={0}; Integrated Security=true; pooling=false;"; - private const string DropDatabaseFormat = "DROP DATABASE {0}"; - private const string CreateDatabaseFormat = "CREATE DATABASE {0}"; - - // For general use - private static SqlConnection s_generalConnection = new SqlConnection(string.Format(CnnStringFormat, DefaultDB)); - - static void Main(string[] args) - { - // 1. Define the retry logic parameters - var options = new SqlRetryLogicOption() - { - NumberOfTries = 5, - MaxTimeInterval = TimeSpan.FromSeconds(20), - DeltaTime = TimeSpan.FromSeconds(1), - AuthorizedSqlCondition = null, - // error number 3702 : Cannot drop database "xxx" because it is currently in use. - TransientErrors = new int[] { 3702 } - }; - - // 2. Create a retry provider - var provider = SqlConfigurableRetryFactory.CreateExponentialRetryProvider(options); - - // define the retrying event to report execution attempts - provider.Retrying += (object s, SqlRetryingEventArgs e) => - { - int attempts = e.RetryCount + 1; - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine($"attempt {attempts} - current delay time:{e.Delay} \n"); - Console.ForegroundColor = ConsoleColor.DarkGray; - if (e.Exceptions[e.Exceptions.Count - 1] is SqlException ex) - { - Console.WriteLine($"{ex.Number}-{ex.Message}\n"); - } - else - { - Console.WriteLine($"{e.Exceptions[e.Exceptions.Count - 1].Message}\n"); - } - - // It is not good practice to do time-consuming tasks inside the retrying event which blocks the running task. - // Use parallel programming patterns to mitigate it. - if (e.RetryCount == provider.RetryLogic.NumberOfTries - 1) - { - Console.WriteLine("This is the last chance to execute the command before throwing the exception."); - Console.WriteLine("Press Enter when you're ready:"); - Console.ReadLine(); - Console.WriteLine("continue ..."); - } - }; - - // Open a general connection. - s_generalConnection.Open(); - - try - { - // Assume the database is creating and other services are going to connect to it. - RetryCommand(provider); - } - catch - { - s_generalConnection.Close(); - // exception is thrown if connecting to the database isn't successful. - throw; - } - s_generalConnection.Close(); - } - - private static void ExecuteCommand(SqlConnection cn, string command) - { - using var cmd = cn.CreateCommand(); - cmd.CommandText = command; - cmd.ExecuteNonQuery(); - } - - private static void FindActiveSessions(SqlConnection cnn, string dbName) - { - using var cmd = cnn.CreateCommand(); - cmd.CommandText = "DECLARE @query NVARCHAR(max) = '';" + Environment.NewLine + - $"SELECT @query = @query + 'KILL ' + CAST(spid as varchar(50)) + ';' FROM sys.sysprocesses WHERE dbid = DB_ID('{dbName}')" + Environment.NewLine + - "SELECT @query AS Active_sessions;"; - var reader = cmd.ExecuteReader(); - if (reader.Read()) - { - Console.ForegroundColor = ConsoleColor.Green; - Console.Write($">> Execute the '{reader.GetString(0)}' command in SQL Server to unblock the running task."); - Console.ResetColor(); - } - reader.Close(); - } - - - How to use with synchronous commands: - - - - private static void RetryCommand(SqlRetryLogicBaseProvider provider) - { - // Change this if you already have a database with the same name in your database. - string dbName = "RetryCommand_TestDatabase"; - - // Subscribe a new event on retry event and discover the active sessions on a database - EventHandler<SqlRetryingEventArgs> retryEvent = (object s, SqlRetryingEventArgs e) => - { - // Run just at first execution - if (e.RetryCount == 1) - { - FindActiveSessions(s_generalConnection, dbName); - Console.WriteLine($"Before exceeding {provider.RetryLogic.NumberOfTries} attempts."); - } - }; - - provider.Retrying += retryEvent; - - // Create a new database. - ExecuteCommand(s_generalConnection, string.Format(CreateDatabaseFormat, dbName)); - Console.WriteLine($"The '{dbName}' database is created."); - - // Open a connection to the newly created database to block it from being dropped. - using var blockingCnn = new SqlConnection(string.Format(CnnStringFormat, dbName)); - blockingCnn.Open(); - Console.WriteLine($"Established a connection to '{dbName}' to block it from being dropped."); - - Console.WriteLine($"Dropping `{dbName}`..."); - // Try to drop the new database. - RetryCommandSync(provider, dbName); - - Console.WriteLine("Command executed successfully."); - - provider.Retrying -= retryEvent; - } - - private static void RetryCommandSync(SqlRetryLogicBaseProvider provider, string dbName) - { - using var cmd = s_generalConnection.CreateCommand(); - cmd.CommandText = string.Format(DropDatabaseFormat, dbName); - // 3. Assign the `provider` to the command - cmd.RetryLogicProvider = provider; - Console.WriteLine("The first attempt, before getting into the retry logic."); - cmd.ExecuteNonQuery(); - } - - - How to use with asynchronous commands: - - - - private static void RetryCommand(SqlRetryLogicBaseProvider provider) - { - // Change this if you already have a database with the same name in your database. - string dbName = "RetryCommand_TestDatabase"; - - // Subscribe to the retry event and discover active sessions in a database - EventHandler<SqlRetryingEventArgs> retryEvent = (object s, SqlRetryingEventArgs e) => - { - // Run just at first execution - if (e.RetryCount == 1) - { - FindActiveSessions(s_generalConnection, dbName); - Console.WriteLine($"Before exceeding {provider.RetryLogic.NumberOfTries} attempts."); - } - }; - - provider.Retrying += retryEvent; - - // Create a new database. - ExecuteCommand(s_generalConnection, string.Format(CreateDatabaseFormat, dbName)); - Console.WriteLine($"The '{dbName}' database is created."); - - // Open a connection to the newly created database to block it from being dropped. - using var blockingCnn = new SqlConnection(string.Format(CnnStringFormat, dbName)); - blockingCnn.Open(); - Console.WriteLine($"Established a connection to '{dbName}' to block it from being dropped."); - - Console.WriteLine("Dropping the database..."); - // Try to drop the new database. - RetryCommandAsync(provider, dbName).Wait(); - - Console.WriteLine("Command executed successfully."); - - provider.Retrying -= retryEvent; - } - - private static async Task RetryCommandAsync(SqlRetryLogicBaseProvider provider, string dbName) - { - using var cmd = s_generalConnection.CreateCommand(); - cmd.CommandText = string.Format(DropDatabaseFormat, dbName); - // 3. Assign the `provider` to the command - cmd.RetryLogicProvider = provider; - Console.WriteLine("The first attempt, before getting into the retry logic."); - await cmd.ExecuteNonQueryAsync(); - } - - - How to use with legacy asynchronous commands: - - - Besides assigning the provider to the command and executing the command, it's possible to run it directly using the following methods: - - - - - - - - - - - - - - - private static void RetryCommand(SqlRetryLogicBaseProvider provider) - { - // Change this if you already have a database with the same name in your database. - string dbName = "RetryCommand_TestDatabase"; - - // Subscribe to the retry event and discover the active sessions in a database - EventHandler<SqlRetryingEventArgs> retryEvent = (object s, SqlRetryingEventArgs e) => - { - // Run just at first execution - if (e.RetryCount == 1) - { - FindActiveSessions(s_generalConnection, dbName); - Console.WriteLine($"Before exceeding {provider.RetryLogic.NumberOfTries} attempts."); - } - }; - - provider.Retrying += retryEvent; - - // Create a new database. - ExecuteCommand(s_generalConnection, string.Format(CreateDatabaseFormat, dbName)); - Console.WriteLine($"The '{dbName}' database is created."); - - // Open a connection to the newly created database to block it from being dropped. - using var blockingCnn = new SqlConnection(string.Format(CnnStringFormat, dbName)); - blockingCnn.Open(); - Console.WriteLine($"Established a connection to '{dbName}' to block it from being dropped."); - - Console.WriteLine("Dropping the database..."); - // Try to drop the new database. - RetryCommandBeginExecuteAsync(provider, dbName).Wait(); - - Console.WriteLine("Command executed successfully."); - - provider.Retrying -= retryEvent; - } - - private static async Task RetryCommandBeginExecuteAsync(SqlRetryLogicBaseProvider provider, string dbName) - { - using var cmd = s_generalConnection.CreateCommand(); - cmd.CommandText = string.Format(DropDatabaseFormat, dbName); - // Execute the BeginExecuteXXX and EndExecuteXXX functions by using Task.Factory.FromAsync(). - // Apply the retry logic by using the ExecuteAsync function of the configurable retry logic provider. - Console.WriteLine("The first attempt, before getting into the retry logic."); - await provider.ExecuteAsync(cmd, () => Task.Factory.FromAsync(cmd.BeginExecuteNonQuery(), cmd.EndExecuteNonQuery)); - } - - @@ -4232,18 +3509,22 @@ The parameters of the Transact-SQL statement or stored procedure. The default is an empty collection. - - The Microsoft .NET Framework Data Provider for SQL Server does not support the question mark (?) placeholder for passing parameters to a SQL Statement or a stored procedure called by a command of . In this case, named parameters must be used. For example: - - - SELECT * FROM Customers WHERE CustomerID = @CustomerID - - - If the parameters in the collection do not match the requirements of the query to be executed, an error may result. - - - For more information, see Configuring parameters. - + [!NOTE] +> If the parameters in the collection do not match the requirements of the query to be executed, an error may result. + +For more information, see [Configuring parameters](https://learn.microsoft.com/sql/connect/ado-net/configure-parameters). + +## Examples +The following example demonstrates how to create a and add parameters to the . + +[!code-csharp[SqlParameterCollection.AddWithValue#1](~/../sqlclient/doc/samples/SqlParameterCollection_AddWithValue.cs#1)] +]]> @@ -4312,78 +3593,29 @@ Creates a prepared version of the command on an instance of SQL Server. - - If is set to , the call to should succeed, although it may cause a no-op. - - - Before you call , specify the data type of each parameter in the statement to be prepared. For each parameter that has a variable length data type, you must set the property to the maximum size needed. returns an error if these conditions are not met. - - - If the database context is changed by executing the Transact-SQL USE <database> statement, or by calling the method, then must be called a second time. - - - If you call an Execute* method after calling , any parameter value that is larger than the value specified by the property is automatically truncated to the original specified size of the parameter, and no truncation errors are returned. - - - Output parameters (whether prepared or not) must have a user-specified data type. If you specify a variable length data type, you must also specify the maximum . - - - Prior to Visual Studio 2010, threw an exception. Beginning in Visual Studio 2010, this method does not throw an exception. - + is set to `StoredProcedure`, the call to should succeed, although it may cause a no-op. + +Before you call , specify the data type of each parameter in the statement to be prepared. For each parameter that has a variable length data type, you must set the property to the maximum size needed. returns an error if these conditions are not met. + +> [!NOTE] +> If the database context is changed by executing the Transact-SQL `USE ` statement, or by calling the method, then must be called a second time. + +If you call an `Execute` method after calling , any parameter value that is larger than the value specified by the property is automatically truncated to the original specified size of the parameter, and no truncation errors are returned. + +Output parameters (whether prepared or not) must have a user-specified data type. If you specify a variable length data type except vector, you must also specify the maximum . + +For vector data types, the property is ignored. The size of the vector is inferred from the of type . + +Prior to Visual Studio 2010, threw an exception. Beginning in Visual Studio 2010, this method does not throw an exception. + +## Examples +The following example demonstrates the use of the method. + +[!code-csharp[SqlCommand.Prepare#1](~/../sqlclient/doc/samples/SqlCommand_Prepare.cs#1)] +]]> - - - The following example demonstrates the use of the method. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - namespace SqlPrepareCS - { - class Program - { - static void Main() - { - string connectionString = "Persist Security Info=False;Integrated Security=SSPI;database=Northwind;server=(local)"; - SqlCommandPrepareEx(connectionString); - Console.ReadLine(); - - } - private static void SqlCommandPrepareEx(string connectionString) - { - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - SqlCommand command = new SqlCommand(null, connection); - - // Create and prepare an SQL statement. - command.CommandText = - "INSERT INTO Region (RegionID, RegionDescription) " + - "VALUES (@id, @desc)"; - SqlParameter idParam = new SqlParameter("@id", SqlDbType.Int, 0); - SqlParameter descParam = new SqlParameter("@desc", SqlDbType.Text, 100); - idParam.Value = 20; - descParam.Value = "First Region"; - command.Parameters.Add(idParam); - command.Parameters.Add(descParam); - - // Call Prepare after setting the Commandtext and Parameters. - command.Prepare(); - command.ExecuteNonQuery(); - - // Change parameter values and call ExecuteNonQuery. - command.Parameters[0].Value = 21; - command.Parameters[1].Value = "Second Region"; - command.ExecuteNonQuery(); - } - } - } - } - - diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlCommandBuilder.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlCommandBuilder.xml index dbe76c374a..52a67f005b 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlCommandBuilder.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlCommandBuilder.xml @@ -229,15 +229,15 @@ The default behavior, when generating parameter names, is to use @p1, @p2, and so on for the various parameters. Passing for the parameter lets you force the command builder to generate parameters based on the column names instead. This succeeds only if the following conditions are met: - + The returned from the GetSchema method call and found in the collection has been specified and its length is equal to or greater than the generated parameter name. - - + + The generated parameter name meets the criteria specified in the returned from the GetSchema method call and found in the collection regular expression. - - + + A returned from the GetSchema method call and found in the collection is specified. - + For more information, see Generating Commands with CommandBuilders. @@ -284,21 +284,21 @@ You can also use as the basis of a modified command. For example, you might call and modify the value, and then explicitly set that on the . - After the Transact-SQL statement is first generated, the application must explicitly call if it changes the statement in any way. Otherwise, the will still be using information from the previous statement, which might not be correct. The Transact-SQL statements are first generated when the application calls either or . + After the Transact-SQL statement is first generated, the application must explicitly call if it changes the statement in any way. Otherwise, the will still be using information from the previous statement, which might not be correct. The Transact-SQL statements are first generated when the application calls either or . The default behavior, when generating parameter names, is to use @p1, @p2, and so on for the various parameters. Passing for the parameter lets you force the command builder to generate parameters based on the column names instead. This succeeds only if the following conditions are met: - + The returned from the GetSchema method call and found in the collection has been specified and its length is equal to or greater than the generated parameter name. - - + + The generated parameter name meets the criteria specified in the returned from the GetSchema method call and found in the collection regular expression. - - + + A returned from the GetSchema method call and found in the collection is specified. - + For more information, see Generating Commands with CommandBuilders. @@ -407,15 +407,15 @@ The default behavior, when generating parameter names, is to use @p1, @p2, and so on for the various parameters. Passing for the parameter lets you force the command builder to generate parameters based on the column names instead. This succeeds only if the following conditions are met: - + The returned from the GetSchema method call and found in the collection has been specified and its length is equal to or greater than the generated parameter name. - - + + The generated parameter name meets the criteria specified in the returned from the GetSchema method call and found in the collection regular expression. - - + + A returned from the GetSchema method call and found in the collection is specified. - + For more information, see Generating Commands with CommandBuilders. @@ -458,9 +458,12 @@ The starting character or characters to use. The default is an empty string. - - Although you cannot change the or properties after an insert, update, or delete command has been generated, you can change their settings after calling the Update method of a DataAdapter. - + + [!NOTE] +> Although you cannot change the or properties after an insert, update, or delete command has been generated, you can change their settings after calling the `Update` method of a DataAdapter. + ]]> + This property cannot be changed after an INSERT, UPDATE, or DELETE command has been generated. @@ -474,9 +477,12 @@ The ending character or characters to use. The default is an empty string. - - Although you cannot change the or properties after an insert, update, or delete operation has been generated, you can change their settings after calling the Update method of a DataAdapter. - + + [!NOTE] +> Although you cannot change the or properties after an insert, update, or delete operation has been generated, you can change their settings after calling the `Update` method of a DataAdapter. + ]]> + This property cannot be changed after an insert, update, or delete command has been generated. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlCommandColumnEncryptionSetting.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlCommandColumnEncryptionSetting.xml index 8bf2f14a02..ff55e35b7a 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlCommandColumnEncryptionSetting.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlCommandColumnEncryptionSetting.xml @@ -6,12 +6,12 @@ - + If Always Encrypted is disabled for a query and the query has parameters that need to be encrypted (parameters that correspond to encrypted columns), the query will fail. - - + + If Always Encrypted is disabled for a query and the query returns results from encrypted columns, the query will return encrypted values. The encrypted values will have the varbinary datatype. - + diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConfigurableRetryFactory.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConfigurableRetryFactory.xml index b7897f045f..730a592b6f 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConfigurableRetryFactory.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConfigurableRetryFactory.xml @@ -127,9 +127,10 @@ A object. - - The inner enumerator includes randomization to prevent multiple instances of the client from performing subsequent retry attempts at the same time. - + +> [!NOTE] +> The inner enumerator includes randomization to prevent multiple instances of the client from performing subsequent retry attempts at the same time. + If the parameter was null. @@ -137,21 +138,21 @@ If at least one of the following conditions occurs: - + is less than 1 or bigger than 60. - - + + is bigger than 120 seconds. - - + + is bigger than 120 seconds. - - + + is bigger than 120 seconds. - - + + is not less than . - + @@ -166,9 +167,10 @@ A object. - - The inner enumerator includes randomization to prevent multiple instances of the client from performing subsequent retry attempts at the same time. - + +> [!NOTE] +> The inner enumerator includes randomization to prevent multiple instances of the client from performing subsequent retry attempts at the same time. + If the parameter was null. @@ -176,21 +178,21 @@ If at least one of the following conditions occurs: - + is less than 1 or bigger than 60. - - + + is bigger than 120 seconds. - - + + is bigger than 120 seconds. - - + + is bigger than 120 seconds. - - + + is not less than . - + @@ -205,9 +207,10 @@ A object. - - The inner enumerator includes randomization to prevent multiple instances of the client from performing subsequent retry attempts at the same time. - + +> [!NOTE] +> The inner enumerator includes randomization to prevent multiple instances of the client from performing subsequent retry attempts at the same time. + If the `retryLogicOption` parameter was null. @@ -215,21 +218,21 @@ If at least one of the following conditions occurs: - + is less than 1 or bigger than 60. - - + + is bigger than 120 seconds. - - + + is bigger than 120 seconds. - - + + is bigger than 120 seconds. - - + + is not less than . - + @@ -241,9 +244,10 @@ A object. - - The returned provider of this function performs a single execution without any retry logic. - + +> [!NOTE] +> The returned provider of this function performs a single execution without any retry logic. + diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml index 41635418bb..11602bd078 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnection.xml @@ -5,82 +5,56 @@ Represents a connection to a SQL Server database. This class cannot be inherited. - - A object represents a unique session to a SQL Server data source. With a client/server database system, it is equivalent to a network connection to the server. is used together with and to increase performance when connecting to a Microsoft SQL Server database. For all third-party SQL Server products and other OLE DB-supported data sources, use . - - - When you create an instance of , all properties are set to their initial values. For a list of these values, see the constructor. - - - See for a list of the keywords in a connection string. - - - If the goes out of scope, it won't be closed. Therefore, you must explicitly close the connection by calling or . and are functionally equivalent. If the connection pooling value Pooling is set to true or yes, the underlying connection is returned back to the connection pool. On the other hand, if Pooling is set to false or no, the underlying connection to the server is actually closed. - - - Login and logout events will not be raised on the server when a connection is fetched from or returned to the connection pool, because the connection is not actually closed when it is returned to the connection pool. For more information, see SQL Server Connection Pooling (ADO.NET). - - - To ensure that connections are always closed, open the connection inside of a using block, as shown in the following code fragment. Doing so ensures that the connection is automatically closed when the code exits the block. - - - Using connection As New SqlConnection(connectionString) - connection.Open() - ' Do work here; connection closed on following line. - End Using - - - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - // Do work here; connection closed on following line. - } - - - To deploy high-performance applications, you must use connection pooling. When you use the .NET Framework Data Provider for SQL Server, you do not have to enable connection pooling because the provider manages this automatically, although you can modify some settings. For more information, see SQL Server Connection Pooling (ADO.NET). - - - If a is generated by the method executing a , the remains open when the severity level is 19 or less. When the severity level is 20 or greater, the server ordinarily closes the . However, the user can reopen the connection and continue. An application that creates an instance of the object can require all direct and indirect callers to have sufficient permission to the code by setting declarative or imperative security demands. makes security demands using the object. Users can verify that their code has sufficient permissions by using the object. Users and administrators can also use the Caspol.exe (Code Access Security Policy Tool) to modify security policy at the machine, user, and enterprise levels. For more information, see Security in .NET. For an example demonstrating how to use security demands, see Code Access Security and ADO.NET. - For more information about handling warning and informational messages from the server, see Connection Events. For more information about SQL Server engine errors and error messages, see Database Engine Events and Errors. - - - You can force TCP instead of shared memory. You can do that by prefixing tcp: to the server name in the connection string or you can use localhost. - - - - - The following example creates a and a . The is opened and set as the for the . The example then calls . To accomplish this, the is passed a SqlConnection and a query string that is a Transact-SQL INSERT statement. The connection is closed automatically when the code exits the using block. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - namespace SqlCommandCS - { - class Program - { - static void Main() - { - string str = "Data Source=(local);Initial Catalog=Northwind;" - + "Integrated Security=SSPI"; - string qs = "SELECT OrderID, CustomerID FROM dbo.Orders;"; - CreateCommand(qs, str); - } - private static void CreateCommand(string queryString, string connectionString) - { - using (SqlConnection connection = new SqlConnection(connectionString)) - { - SqlCommand command = new SqlCommand(queryString, connection); - command.Connection.Open(); - command.ExecuteNonQuery(); - } - } - } - } - - + object represents a unique session to a SQL Server data source. With a client/server database system, it is equivalent to a network connection to the server. is used together with and to increase performance when connecting to a Microsoft SQL Server database. For all third-party SQL Server products and other OLE DB-supported data sources, use . + +When you create an instance of , all properties are set to their initial values. For a list of these values, see the constructor. + +See for a list of the keywords in a connection string. + +If the goes out of scope, it won't be closed. Therefore, you must explicitly close the connection by calling `Close` or `Dispose`. `Close` and `Dispose` are functionally equivalent. If the connection pooling value `Pooling` is set to `true` or `yes`, the underlying connection is returned back to the connection pool. On the other hand, if `Pooling` is set to `false` or `no`, the underlying connection to the server is actually closed. + +> [!NOTE] +> Login and logout events will not be raised on the server when a connection is fetched from or returned to the connection pool, because the connection is not actually closed when it is returned to the connection pool. For more information, see [SQL Server Connection Pooling (ADO.NET)](https://learn.microsoft.com/sql/connect/ado-net/sql-server-connection-pooling). + +To ensure that connections are always closed, open the connection inside of a `using` block, as shown in the following code fragment. Doing so ensures that the connection is automatically closed when the code exits the block. + +```vb +Using connection As New SqlConnection(connectionString) +connection.Open() +' Do work here; connection closed on following line. +End Using + +``` + +```csharp +using (SqlConnection connection = new SqlConnection(connectionString)) +{ + connection.Open(); + // Do work here; connection closed on following line. +} +``` + +> [!NOTE] +> To deploy high-performance applications, you must use connection pooling. When you use the .NET Framework Data Provider for SQL Server, you do not have to enable connection pooling because the provider manages this automatically, although you can modify some settings. For more information, see [SQL Server Connection Pooling (ADO.NET)](https://learn.microsoft.com/sql/connect/ado-net/sql-server-connection-pooling). + +If a is generated by the method executing a , the remains open when the severity level is 19 or less. When the severity level is 20 or greater, the server ordinarily closes the . However, the user can reopen the connection and continue. + +An application that creates an instance of the object can require all direct and indirect callers to have sufficient permission to the code by setting declarative or imperative security demands. makes security demands using the object. Users can verify that their code has sufficient permissions by using the object. Users and administrators can also use the [Caspol.exe (Code Access Security Policy Tool)](https://learn.microsoft.com/dotnet/framework/tools/caspol-exe-code-access-security-policy-tool) to modify security policy at the machine, user, and enterprise levels. For more information, see [Security in .NET](https://learn.microsoft.com/dotnet/standard/security/). For an example demonstrating how to use security demands, see [Code Access Security and ADO.NET](https://learn.microsoft.com/dotnet/framework/data/adonet/code-access-security). + +For more information about handling warning and informational messages from the server, see [Connection Events](https://learn.microsoft.com/sql/connect/ado-net/connection-events). For more information about SQL Server engine errors and error messages, see [Database Engine Events and Errors](https://learn.microsoft.com/sql/relational-databases/errors-events/database-engine-events-and-errors). + +> [!CAUTION] +> You can force TCP instead of shared memory. You can do that by prefixing tcp: to the server name in the connection string or you can use localhost. + +## Examples +The following example creates a and a . The is opened and set as the for the . The example then calls . To accomplish this, the is passed a SqlConnection and a query string that is a Transact-SQL INSERT statement. The connection is closed automatically when the code exits the using block. + +[!code-csharp[SqlCommand_ExecuteNonQuery Example#1](~/../sqlclient/doc/samples/SqlCommand_ExecuteNonQuery.cs#1)] + +]]> + @@ -262,8 +236,35 @@ The access token for the connection. + + The + is combined with other conflicting authentication configurations. + - The AccessToken is a part of the connection pool key. Care should be taken when using this property to manage your own access token. The application is responsible for knowing when the token expires and connections from the pool should no longer be used. If you set a minimum pool size > 0 along with AccessToken, you must call after the access token expires to ensure the connection pool doesn't maintain those connections indefinitely. + + The AccessToken is a part of the connection pool key. Supplying a + refreshed access token to a new connection, with all other properties + the same as other existing connections, will place that new connection + into a new pool. Care should be taken when using this property to + manage your own access token. Consider using the + + property instead, which will not consider refreshed access tokens as + distinct for the purposes of connection pooling. + + + The application is responsible for knowing when the access token + expires and the connections from the pool should no longer be used. If + you set a minimum pool size > 0 along with an AccessToken, you must + call + + after the access token expires to ensure the connection pool doesn't + maintain the physical connections (created by the pool) indefinitely. + + + This property is mutually exclusive with the + + property, among others. + @@ -271,7 +272,7 @@ Gets or sets the access token callback for the connection. - The Func that takes a and and returns a . + The Func that takes a and and returns a . @@ -297,10 +298,10 @@ const string defaultScopeSuffix = "/.default"; - // Reuse credential objects to take advantage of underlying token caches + // Reuse credential objects to take advantage of underlying token caches. private static ConcurrentDictionar<string, DefaultAzureCredential> credentials = new ConcurrentDictionary<string, DefaultAzureCredential>(); - // Use a shared callback function for connections that should be in the same connection pool + // Use a shared callback function for connections that should be in the same connection pool. private static Func<SqlAuthenticationParameters, CancellationToken, Task<SqlAuthenticationToken>> myAccessTokenCallback = async (authParams, cancellationToken) => { @@ -311,7 +312,7 @@ DefaultAzureCredentialOptions options = new DefaultAzureCredentialOptions(); options.ManagedIdentityClientId = authParams.UserId; - // Reuse the same credential object if we are using the same MI Client ID + // Reuse the same credential object if we are using the same MI Client ID. AccessToken token = await credentials.GetOrAdd(authParams.UserId, new DefaultAzureCredential(options)).GetTokenAsync( new TokenRequestContext(new string[] { scope }), cancellationToken); @@ -343,6 +344,26 @@ The is combined with other conflicting authentication configurations. + + + The AccessTokenCallback function itself is a part of the connection + pool key. The same callback function should always provide a valid + access token of the same security context given the same + SqlAuthenticationParameters. When multiple connections use the same + callback function, and all other properties that comprise the pool key + are the same, they will be grouped into the same connection pool. + + + When using a token callback function, the connection manages + refreshing the tokens returned by the callback. The application is + not responsible for knowing when tokens expire. + + + This property is mutually exclusive with the + + property, among others. + + @@ -366,98 +387,30 @@ An object representing the new transaction. - - This command maps to the SQL Server implementation of BEGIN TRANSACTION.You must explicitly commit or roll back the transaction using the or method. To make sure that the .NET Framework Data Provider for SQL Server transaction management model performs correctly, avoid using other transaction management models, such as the one provided by SQL Server. - - - If you do not specify an isolation level, the default isolation level is used. To specify an isolation level with the method, use the overload that takes the iso parameter (). The isolation level set for a transaction persists after the transaction is completed and until the connection is closed or disposed. Setting the isolation level to Snapshot in a database where the snapshot isolation level is not enabled does not throw an exception. The transaction will complete using the default isolation level. - - - If a transaction is started and a level 16 or higher error occurs on the server, the transaction will not be rolled back until the method is invoked. No exception is thrown on . - - - When your query returns a large amount of data and calls , a is thrown because SQL Server does not allow parallel transactions when using MARS. To avoid this problem, always associate a transaction with the command, the connection, or both before any readers are open. - - - For more information on SQL Server transactions, see Transactions (Transact-SQL). - - - - - The following example creates a and a . It also demonstrates how to use the , a , and methods. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - namespace Transaction1Cs - { - class Program - { - static void Main() - { - string connectionString = - "Persist Security Info=False;Integrated Security=SSPI;database=Northwind;server=(local)"; - ExecuteSqlTransaction(connectionString); - Console.ReadLine(); - } - private static void ExecuteSqlTransaction(string connectionString) - { - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - SqlCommand command = connection.CreateCommand(); - SqlTransaction transaction; - - // Start a local transaction. - transaction = connection.BeginTransaction(); - - // Must assign both transaction object and connection - // to Command object for a pending local transaction - command.Connection = connection; - command.Transaction = transaction; - - try - { - command.CommandText = - "Insert into Region (RegionID, RegionDescription) VALUES (100, 'Description')"; - command.ExecuteNonQuery(); - command.CommandText = - "Insert into Region (RegionID, RegionDescription) VALUES (101, 'Description')"; - command.ExecuteNonQuery(); - - // Attempt to commit the transaction. - transaction.Commit(); - Console.WriteLine("Both records are written to database."); - } - catch (Exception ex) - { - Console.WriteLine("Commit Exception Type: {0}", ex.GetType()); - Console.WriteLine(" Message: {0}", ex.Message); - - // Attempt to roll back the transaction. - try - { - transaction.Rollback(); - } - catch (Exception ex2) - { - // This catch block will handle any errors that may have occurred - // on the server that would cause the rollback to fail, such as - // a closed connection. - Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType()); - Console.WriteLine(" Message: {0}", ex2.Message); - } - } - } - } - } - } - - + or method. To make sure that the .NET Framework Data Provider for SQL Server transaction management model performs correctly, avoid using other transaction management models, such as the one provided by SQL Server. + +> [!NOTE] +> If you do not specify an isolation level, the default isolation level is used. To specify an isolation level with the method, use the overload that takes the `iso` parameter (). The isolation level set for a transaction persists after the transaction is completed and until the connection is closed or disposed. Setting the isolation level to **Snapshot** in a database where the snapshot isolation level is not enabled does not throw an exception. The transaction will complete using the default isolation level. + +> [!CAUTION] +> If a transaction is started and a level 16 or higher error occurs on the server, the transaction will not be rolled back until the method is invoked. No exception is thrown on **ExecuteReader**. + +> [!CAUTION] +> When your query returns a large amount of data and calls `BeginTransaction`, a is thrown because SQL Server does not allow parallel transactions when using MARS. To avoid this problem, always associate a transaction with the command, the connection, or both before any readers are open. + +For more information on SQL Server transactions, see [Transactions (Transact-SQL)](https://learn.microsoft.com/sql/t-sql/language-elements/transactions-transact-sql). + +## Examples +The following example creates a and a . It also demonstrates how to use the , a , and methods. + +[!code-csharp[SqlConnection_BeginTransaction Example#1](~/../sqlclient/doc/samples/SqlConnection_BeginTransaction.cs#1)] + +]]> + Parallel transactions are not allowed when using Multiple Active Result Sets (MARS). @@ -476,91 +429,27 @@ An object representing the new transaction. - - This command maps to the SQL Server implementation of BEGIN TRANSACTION. You must explicitly commit or roll back the transaction using the or method. To make sure that the .NET Framework Data Provider for SQL Server transaction management model performs correctly, avoid using other transaction management models, such as the one provided by SQL Server. - - - After a transaction is committed or rolled back, the isolation level of the transaction persists for all subsequent commands that are in autocommit mode (the SQL Server default). This can produce unexpected results, such as an isolation level of REPEATABLE READ persisting and locking other users out of a row. To reset the isolation level to the default (READ COMMITTED), execute the Transact-SQL SET TRANSACTION ISOLATION LEVEL READ COMMITTED statement, or call followed immediately by . For more information on SQL Server isolation levels, see Transaction Isolation Levels. - - - For more information on SQL Server transactions, see Transactions (Transact-SQL). - - - When your query returns a large amount of data and calls , a is thrown because SQL Server does not allow parallel transactions when using MARS. To avoid this problem, always associate a transaction with the command, the connection, or both before any readers are open. - + or method. To make sure that the .NET Framework Data Provider for SQL Server transaction management model performs correctly, avoid using other transaction management models, such as the one provided by SQL Server. + +> [!NOTE] +> After a transaction is committed or rolled back, the isolation level of the transaction persists for all subsequent commands that are in autocommit mode (the SQL Server default). This can produce unexpected results, such as an isolation level of REPEATABLE READ persisting and locking other users out of a row. To reset the isolation level to the default (READ COMMITTED), execute the Transact-SQL SET TRANSACTION ISOLATION LEVEL READ COMMITTED statement, or call followed immediately by . For more information on SQL Server isolation levels, see [Transaction Isolation Levels](https://learn.microsoft.com/sql/t-sql/language-elements/transaction-isolation-levels). + +For more information on SQL Server transactions, see [Transactions (Transact-SQL)](https://learn.microsoft.com/sql/t-sql/language-elements/transactions-transact-sql). + +> [!CAUTION] +> When your query returns a large amount of data and calls `BeginTransaction`, a is thrown because SQL Server does not allow parallel transactions when using MARS. To avoid this problem, always associate a transaction with the command, the connection, or both before any readers are open. + +## Examples +The following example creates a and a . It also demonstrates how to use the , a , and methods. + +[!code-csharp[SqlConnection_BeginTransaction1 Example#1](~/../sqlclient/doc/samples/SqlConnection_BeginTransaction1.cs#1)] + +]]> - - - The following example creates a and a . It also demonstrates how to use the , a , and methods. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string connectionString = - "Persist Security Info=False;Integrated Security=SSPI;database=Northwind;server=(local)"; - ExecuteSqlTransaction(connectionString); - Console.ReadLine(); - } - - private static void ExecuteSqlTransaction(string connectionString) - { - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - SqlCommand command = connection.CreateCommand(); - SqlTransaction transaction; - - // Start a local transaction. - transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); - - // Must assign both transaction object and connection - // to Command object for a pending local transaction - command.Connection = connection; - command.Transaction = transaction; - - try - { - command.CommandText = - "Insert into Region (RegionID, RegionDescription) VALUES (100, 'Description')"; - command.ExecuteNonQuery(); - command.CommandText = - "Insert into Region (RegionID, RegionDescription) VALUES (101, 'Description')"; - command.ExecuteNonQuery(); - transaction.Commit(); - Console.WriteLine("Both records are written to database."); - } - catch (Exception e) - { - try - { - transaction.Rollback(); - } - catch (SqlException ex) - { - if (transaction.Connection != null) - { - Console.WriteLine("An exception of type " + ex.GetType() + - " was encountered while attempting to roll back the transaction."); - } - } - - Console.WriteLine("An exception of type " + e.GetType() + - " was encountered while inserting the data."); - Console.WriteLine("Neither record was written to database."); - } - } - } - } - - Parallel transactions are not allowed when using Multiple Active Result Sets (MARS). @@ -579,90 +468,28 @@ An object representing the new transaction. - - This command maps to the SQL Server implementation of BEGIN TRANSACTION. The length of the parameter must not exceed 32 characters; otherwise an exception will be thrown. The value in the parameter can be used in later calls to and in the savePoint parameter of the method. You must explicitly commit or roll back the transaction using the or method. To make sure that the .NET Data Provider for SQL Server transaction management model performs correctly, avoid using other transaction management models, such as the one provided by SQL Server. For more information on SQL Server transactions, see Transactions (Transact-SQL). - - - When your query returns a large amount of data and calls , a is thrown because SQL Server does not allow parallel transactions when using MARS. To avoid this problem, always associate a transaction with the command, the connection, or both before any readers are open. - + and in the `savePoint` parameter of the method. + +You must explicitly commit or roll back the transaction using the or method. To make sure that the .NET Data Provider for SQL Server transaction management model performs correctly, avoid using other transaction management models, such as the one provided by SQL Server. + +For more information on SQL Server transactions, see [Transactions (Transact-SQL)](https://learn.microsoft.com/sql/t-sql/language-elements/transactions-transact-sql). + +> [!CAUTION] +> When your query returns a large amount of data and calls `BeginTransaction`, a is thrown because SQL Server does not allow parallel transactions when using MARS. To avoid this problem, always associate a transaction with the command, the connection, or both before any readers are open. + +## Examples +The following example creates a and a . It also demonstrates how to use the , a , and methods. + +[!code-csharp[SqlConnection_BeginTransaction2 Example#1](~/../sqlclient/doc/samples/SqlConnection_BeginTransaction2.cs#1)] + +]]> - - - The following example creates a and a . It also demonstrates how to use the , a , and methods. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - namespace Transaction1CS - { - class Program - { - static void Main() - { - string connectionString = - "Persist Security Info=False;Integrated Security=SSPI;database=Northwind;server=(local)"; - ExecuteSqlTransaction(connectionString); - Console.ReadLine(); - } - - private static void ExecuteSqlTransaction(string connectionString) - { - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - SqlCommand command = connection.CreateCommand(); - SqlTransaction transaction; - - // Start a local transaction. - transaction = connection.BeginTransaction("SampleTransaction"); - - // Must assign both transaction object and connection - // to Command object for a pending local transaction - command.Connection = connection; - command.Transaction = transaction; - - try - { - command.CommandText = - "Insert into Region (RegionID, RegionDescription) VALUES (100, 'Description')"; - command.ExecuteNonQuery(); - command.CommandText = - "Insert into Region (RegionID, RegionDescription) VALUES (101, 'Description')"; - command.ExecuteNonQuery(); - - // Attempt to commit the transaction. - transaction.Commit(); - Console.WriteLine("Both records are written to database."); - } - catch (Exception ex) - { - Console.WriteLine("Commit Exception Type: {0}", ex.GetType()); - Console.WriteLine(" Message: {0}", ex.Message); - - // Attempt to roll back the transaction. - try - { - transaction.Rollback("SampleTransaction"); - } - catch (Exception ex2) - { - // This catch block will handle any errors that may have occurred - // on the server that would cause the rollback to fail, such as - // a closed connection. - Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType()); - Console.WriteLine(" Message: {0}", ex2.Message); - } - } - } - } - } - } - - Parallel transactions are not allowed when using Multiple Active Result Sets (MARS). @@ -684,95 +511,29 @@ An object representing the new transaction. - - This command maps to the SQL Server implementation of BEGIN TRANSACTION. The value in the parameter can be used in later calls to and in the savePoint parameter of the method. You must explicitly commit or roll back the transaction using the or method. To make sure that the SQL Server transaction management model performs correctly, avoid using other transaction management models, such as the one provided by SQL Server. - - - After a transaction is committed or rolled back, the isolation level of the transaction persists for all subsequent commands that are in autocommit mode (the SQL Server default). This can produce unexpected results, such as an isolation level of REPEATABLE READ persisting and locking other users out of a row. To reset the isolation level to the default (READ COMMITTED), execute the Transact-SQL SET TRANSACTION ISOLATION LEVEL READ COMMITTED statement, or call followed immediately by . For more information on SQL Server isolation levels, see Transaction Isolation Levels. - - - For more information on SQL Server transactions, see Transactions (Transact-SQL). - - - When your query returns a large amount of data and calls , a is thrown because SQL Server does not allow parallel transactions when using MARS. To avoid this problem, always associate a transaction with the command, the connection, or both before any readers are open. - - - - - The following example creates a and a . It also demonstrates how to use the , a , and methods. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - namespace Transaction1CS - { - class Program - { - static void Main() - { - string connectionString = - "Persist Security Info=False;Integrated Security=SSPI;database=Northwind;server=(local)"; - ExecuteSqlTransaction(connectionString); - Console.ReadLine(); - } + - +The value in the `transactionName` parameter can be used in later calls to and in the `savePoint` parameter of the method. + +You must explicitly commit or roll back the transaction using the or method. To make sure that the SQL Server transaction management model performs correctly, avoid using other transaction management models, such as the one provided by SQL Server. + +> [!NOTE] +> After a transaction is committed or rolled back, the isolation level of the transaction persists for all subsequent commands that are in autocommit mode (the SQL Server default). This can produce unexpected results, such as an isolation level of REPEATABLE READ persisting and locking other users out of a row. To reset the isolation level to the default (READ COMMITTED), execute the Transact-SQL SET TRANSACTION ISOLATION LEVEL READ COMMITTED statement, or call followed immediately by . For more information on SQL Server isolation levels, see [Transaction Isolation Levels](https://learn.microsoft.com/sql/t-sql/language-elements/transaction-isolation-levels). + +For more information on SQL Server transactions, see [Transactions (Transact-SQL)](https://learn.microsoft.com/sql/t-sql/language-elements/transactions-transact-sql). + +> [!CAUTION] +> When your query returns a large amount of data and calls `BeginTransaction`, a is thrown because SQL Server does not allow parallel transactions when using MARS. To avoid this problem, always associate a transaction with the command, the connection, or both before any readers are open. + +## Examples +The following example creates a and a . It also demonstrates how to use the , a , and methods. + +[!code-csharp[SqlConnection_BeginTransaction3 Example#1](~/../sqlclient/doc/samples/SqlConnection_BeginTransaction3.cs#1)] + +]]> + Parallel transactions are not allowed when using Multiple Active Result Sets (MARS). @@ -1019,12 +780,12 @@ - + The connection string includes the option to use integrated security. - - + + The exceeds 128 characters. - + @@ -1046,18 +807,18 @@ - + The connection string contains any combination of UserId, Password, or Integrated Security=true. - - + + is greater than 128 characters. - - + + is not read only. - - + + is an empty string. - + @@ -1080,7 +841,7 @@ Empties the connection pool associated with the specified connection. - clears the connection pool that is associated with the . If additional connections associated with are in use at the time of the call, they are marked appropriately and are discarded (instead of being returned to the pool) when is called on them. + clears the connection pool that is associated with the . If additional connections associated with are in use at the time of the call, they are marked appropriately and are discarded (instead of being returned to the pool) when is called on them. @@ -1104,63 +865,30 @@ Closes the connection to the database. This is the preferred method of closing any open connection. - - The method rolls back any pending transactions. It then releases the connection to the connection pool, or closes the connection if connection pooling is disabled. - - - Pending transactions started using Transact-SQL or are automatically rolled back when the connection is reset if connection pooling is enabled. If connection pooling is off, the transaction is rolled back after SqlConnection.Close is called. Transactions started through are controlled through the infrastructure, and are not affected by SqlConnection.Close. - - - An application can call more than one time. No exception is generated. If the goes out of scope, it won't be closed. Therefore, you must explicitly close the connection by calling Close or Dispose. Close and Dispose are functionally equivalent. If the connection pooling value Pooling is set to true or yes, the underlying connection is returned back to the connection pool. On the other hand, if Pooling is set to false or no, the underlying connection to the server is closed. - - - Login and logout events will not be raised on the server when a connection is fetched from or returned to the connection pool, because the connection is not actually closed when it is returned to the connection pool. For more information, see SQL Server Connection Pooling (ADO.NET). - - - Do not call Close or Dispose on a Connection, a DataReader, or any other managed object in the Finalize method of your class. In a finalizer, you should only release unmanaged resources that your class owns directly. If your class does not own any unmanaged resources, do not include a Finalize method in your class definition. For more information, see Garbage Collection. - + method rolls back any pending transactions. It then releases the connection to the connection pool, or closes the connection if connection pooling is disabled. + +> [!NOTE] +> Pending transactions started using Transact-SQL or are automatically rolled back when the connection is reset if connection pooling is enabled. If connection pooling is off, the transaction is rolled back after `SqlConnection.Close` is called. Transactions started through are controlled through the `System.Transactions` infrastructure, and are not affected by `SqlConnection.Close`. + +An application can call more than one time. No exception is generated. + +If the goes out of scope, it won't be closed. Therefore, you must explicitly close the connection by calling `Close` or `Dispose`. `Close` and `Dispose` are functionally equivalent. If the connection pooling value `Pooling` is set to `true` or `yes`, the underlying connection is returned back to the connection pool. On the other hand, if `Pooling` is set to `false` or `no`, the underlying connection to the server is closed. + +> [!NOTE] +> Login and logout events will not be raised on the server when a connection is fetched from or returned to the connection pool, because the connection is not actually closed when it is returned to the connection pool. For more information, see [SQL Server Connection Pooling (ADO.NET)](https://learn.microsoft.com/sql/connect/ado-net/sql-server-connection-pooling). + +> [!CAUTION] +> Do not call `Close` or `Dispose` on a Connection, a DataReader, or any other managed object in the `Finalize` method of your class. In a finalizer, you should only release unmanaged resources that your class owns directly. If your class does not own any unmanaged resources, do not include a `Finalize` method in your class definition. For more information, see [Garbage Collection](https://learn.microsoft.com/dotnet/standard/garbage-collection/). + +## Examples +The following example creates a , opens it, displays some of its properties. The connection is automatically closed at the end of the `using` block. + +[!code-csharp[SqlConnection_Open Example#1](~/../sqlclient/doc/samples/SqlConnection_Open.cs#1)] + +]]> - - - The following example creates a , opens it, displays some of its properties. The connection is automatically closed at the end of the using block. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program1 - { - static void Main() - { - string s = GetConnectionString(); - - OpenSqlConnection(s); - Console.ReadLine(); - } - - private static void OpenSqlConnection(string connectionString) - { - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - Console.WriteLine("ServerVersion: {0}", connection.ServerVersion); - Console.WriteLine("State: {0}", connection.State); - } - } - - static private string GetConnectionString() - { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file, using the - // System.Configuration.ConfigurationSettings.AppSettings property - return "Data Source=(local);Initial Catalog=AdventureWorks;" - + "Integrated Security=SSPI;"; - } - } - - The connection-level error that occurred while opening the connection. @@ -1191,828 +919,130 @@ The list of trusted master key paths for the column encryption. - - - - Gets the default wait time (in seconds) before terminating the attempt to execute a command and generating an error. The default is 30 seconds. - - - The time in seconds to wait for the command to execute. The default is 30 seconds. - - - You can set the default wait time by using the Command Timeout keyword in the connection string. A value of 0 indicates no limit (an attempt to execute a command will wait indefinitely). - - - - - Gets or sets the string used to open a SQL Server database. - - - The connection string that includes the source database name, and other parameters needed to establish the initial connection. The default value is an empty string. - - - - The is similar to an OLE DB connection string, but is not identical. Unlike OLE DB or ADO, the connection string that is returned is the same as the user-set , minus security information if the Persist Security Info value is set to false (default). The .NET Framework Data Provider for SQL Server does not persist or return the password in a connection string unless you set Persist Security Info to true. You can use the property to connect to a database. The following example illustrates a typical connection string. - - - "Persist Security Info=False;Integrated Security=true;Initial Catalog=Northwind;server=(local)" - - - Use the new to construct valid connection strings at run time. For more information, see Connection String Builders. The property can be set only when the connection is closed. Many of the connection string values have corresponding read-only properties. When the connection string is set, these properties are updated, except when an error is detected. In this case, none of the properties are updated. properties return only those settings that are contained in the . To connect to a local computer, specify "(local)" for the server. If a server name is not specified, a connection will be attempted to the default instance on the local computer. Resetting the on a closed connection resets all connection string values (and related properties) including the password. For example, if you set a connection string that includes "Database= AdventureWorks", and then reset the connection string to Data Source=myserver;Integrated Security=true, the property is no longer set to "AdventureWorks". The connection string is parsed immediately after being set. If errors in syntax are found when parsing, a runtime exception, such as , is generated. Other errors can be found only when an attempt is made to open the connection. The basic format of a connection string includes a series of keyword/value pairs separated by semicolons. The equal sign (=) connects each keyword and its value. To include values that contain a semicolon, single-quote character, or double-quote character, the value must be enclosed in double quotation marks. If the value contains both a semicolon and a double-quote character, the value can be enclosed in single quotation marks. The single quotation mark is also useful if the value starts with a double-quote character. Conversely, the double quotation mark can be used if the value starts with a single quotation mark. If the value contains both single-quote and double-quote characters, the quotation mark character used to enclose the value must be doubled every time it occurs within the value. To include preceding or trailing spaces in the string value, the value must be enclosed in either single quotation marks or double quotation marks. Any leading or trailing spaces around integer, Boolean, or enumerated values are ignored, even if enclosed in quotation marks. However, spaces within a string literal keyword or value are preserved. Single or double quotation marks may be used within a connection string without using delimiters (for example, Data Source= my'Server or Data Source= my"Server), unless a quotation mark character is the first or last character in the value. Keywords are not case-sensitive. The following table lists the valid names for keyword values within the . - - - - Keyword - Default - Description - - - Addr - N/A - Synonym of Data Source. - - - Address - N/A - Synonym of Data Source. - - - App - N/A - Synonym of Application Name. - - - - Application Intent or ApplicationIntent - - ReadWrite - - - Declares the application workload type when connecting to a server. Possible values are ReadOnly and ReadWrite. For example: ApplicationIntent=ReadOnly - - - For more information about SqlClient support for Always On Availability Groups, see SqlClient Support for High Availability, Disaster Recovery. - - - - - Application Name - N/A - - - The name of the application. If no application name is provided, 'Framework Microsoft SqlClient Data Provider' when running on .NET Framework and 'Core Microsoft SqlClient Data Provider' otherwise. - - - An application name can be 128 characters or fewer. - - - - - - AttachDBFilename or Extended Properties or Initial File Name - - N/A - - - The name of the primary database file, including the full path name of an attachable database. AttachDBFilename is only supported for primary data files with an .mdf extension. - - - If the value of the AttachDBFileName key is specified in the connection string, the database is attached and becomes the default database for the connection. - - - If this key is not specified and if the database was previously attached, the database will not be reattached. The previously attached database will be used as the default database for the connection. - - - If this key is specified together with the AttachDBFileName key, the value of this key will be used as the alias. However, if the name is already used in another attached database, the connection will fail. - - - The path may be absolute or relative by using the DataDirectory substitution string. If DataDirectory is used, the database file must exist within a subdirectory of the directory pointed to by the substitution string. Note: Remote server, HTTP, and UNC path names are not supported. - - - The database name must be specified with the keyword 'database' (or one of its aliases) as in the following: - - - AttachDbFileName=|DataDirectory|\data\YourDB.mdf;integrated security=true;database=YourDatabase - - - An error will be generated if a log file exists in the same directory as the data file and the 'database' keyword is used when attaching the primary data file. In this case, remove the log file. Once the database is attached, a new log file will be automatically generated based on the physical path. - - - - - Attestation Protocol - NotSpecified - - - Gets or sets the value of Attestation Protocol. - - - When no value is specified, secure enclaves are disabled on the connection. - - - Valid values are: - - - AAS - HGS - None (Only valid in v3.1 and v4.1+) - - - - - Authentication - N/A - - - The authentication method used for Connect with Microsoft Entra to Azure SQL resources. - - - Valid values are: - - - Active Directory Integrated - Active Directory Interactive - Active Directory Password - Active Directory Service Principal - Active Directory Device Code Flow - Active Directory Managed Identity - Active Directory MSI - Active Directory Default - Sql Password - - - For additional information see Using Azure Active Directory authentication with SqlClient. - - - - - Column Encryption Setting - disabled - - Enables or disables Always Encrypted functionality for the connection. Supported values are: enabled and disabled - - - - Command Timeout - 30 - - - The default wait time (in seconds) before terminating the attempt to execute a command and generating an error. - - - Valid values are greater than or equal to 0 and less than or equal to 2147483647. - - - - - - Connect Retry Count or ConnectRetryCount - - 1 - - - Controls the number of reconnection attempts after the client identifies an idle connection failure. Valid values are 0 to 255. 0 means do not attempt to reconnect (disable connection resiliency). - - - Note: Since version 5.x the default value for non Azure endpoints is 1. For Azure SQL endpoints, the default is 2. For Azure SQL serverless or on demand endpoints, the default is 5 to improve connection success for connections to an idle or paused instance. - - - For additional information about idle connection resiliency, see .NET SqlConnection parameters for connection retry and Technical Article - Idle Connection Resiliency. - - - - - - Connect Retry Interval or ConnectRetryInterval - - 10 - - - Specifies the time between each connection retry attempt (ConnectRetryCount). Valid values are 1 to 60 seconds (default=10), applied after the first reconnection attempt. When a broken connection is detected, the client immediately attempts to reconnect; this is the first reconnection attempt and only occurs if ConnectRetryCount is greater than 0. If the first reconnection attempt fails and ConnectRetryCount is greater than 1, the client waits ConnectRetryInterval to try the second and subsequent reconnection attempts. - - - For additional information about idle connection resiliency, see .NET SqlConnection parameters for connection retry and Technical Article - Idle Connection Resiliency. - - - - - - Connect Timeout or Connection Timeout or Timeout - - 15 - - - The length of time (in seconds) to wait for a connection to the server before terminating the attempt and generating an error. - - - Valid values are greater than or equal to 0 and less than or equal to 2147483647. - - - When opening a connection to a Azure SQL Database, set the connection timeout to 30 seconds. - - - - - - Current Language or Language - - N/A - - - Sets the language used for database server warning or error messages. - - - The language name can be 128 characters or fewer. - - - - - - Data Source or Server or Address or Addr or Network Address - - N/A - - - The name or network address of the instance of SQL Server to which to connect. The port number can be specified after the server name: server=tcp:servername, portnumber - - - When specifying a local instance, always use (local). To force a protocol, add one of the following prefixes: np:(local), tcp:(local), lpc:(local) - - - You can also connect to a LocalDB database as follows: server=(localdb)\\myInstance - - - For more information about LocalDB, see SqlClient Support for LocalDB. - - - Data Source must use the TCP format or the Named Pipes format. - - - TCP format is as follows: - - - tcp:\<host name>\\<instance name>\ - tcp:\<host name>,\<TCP/IP port number> - - - The TCP format must start with the prefix "tcp:" and is followed by the database instance, as specified by a host name and an instance name. This format is not applicable when connecting to Azure SQL Database. TCP is automatically selected for connections to Azure SQL Database when no protocol is specified. - - - The host name MUST be specified in one of the following ways: - - - NetBIOSName - IPv4Address - IPv6Address - - - The instance name is used to resolve to a particular TCP/IP port number on which a database instance is hosted. Alternatively, specifying a TCP/IP port number directly is also allowed. If both instance name and port number are not present, the default database instance is used. - - - The Named Pipes format is as follows: - - - np:\\\\<host name>\pipe\\<pipe name> - - - The Named Pipes format MUST start with the prefix "np:" and is followed by a named pipe name.

    - The host name MUST be specified in one of the following ways: -
    - - NetBIOSName - IPv4Address - IPv6Address - - - The pipe name is used to identify the database instance to which the .NET application will connect. - - - If the value of the Network key is specified, the prefixes "tcp:" and "np:" should not be specified. Note: You can force the use of TCP instead of shared memory, either by prefixing tcp: to the server name in the connection string, or by using localhost. - -
    -
    - - Enclave Attestation Url - N/A - Gets or sets the enclave attestation URL to be used with enclave based Always Encrypted. - - - Encrypt - - true in 4.0 and above

    - false in 3.x and below -
    - - - Recognized values are: - - - - versions 1 - 4: true / yes and false / no - - - versions 5+: true / yes / mandatory, false / no / optional and strict. When true, TLS encryption is used for all data sent between the client and server if the server has a certificate installed. When strict, TDS 8.0 TLS encryption is used and the TrustServerCertificate setting is ignored and treated as false. For more information, see Connection String Syntax. - - - - When Encrypt is mandatory or strict and TrustServerCertificate is false, the server name (or IP address) in a server's certificate must exactly match the server name (or IP address) specified in the connection string. Otherwise, the connection attempt will fail. - - -
    - - Enlist - 'true' - - true indicates that the SQL Server connection pooler automatically enlists the connection in the creation thread's current transaction context. - - - - Failover Partner - N/A - - - The name of the failover partner server where database mirroring is configured. - - - If the value of this key is "", then Initial Catalog must be present, and its value must not be "". - - - The server name can be 128 characters or fewer. - - - If you specify a failover partner but the failover partner server is not configured for database mirroring and the primary server (specified with the Server keyword) is not available, then the connection will fail. - - - If you specify a failover partner and the primary server is not configured for database mirroring, the connection to the primary server (specified with the Server keyword) will succeed if the primary server is available. - - - - - - Failover Partner SPN or FailoverPartnerSPN - - N/A - - The SPN for the failover partner. The default value is an empty string, which causes SqlClient to use the default, driver-generated SPN. (Only available in v5.0+) - - - - - Host Name In Certificate or HostNameInCertificate - - N/A - - The host name to use when validating the server certificate. When not specified, the server name from the Data Source is used for certificate validation. (Only available in v5.0+) - - - - - Server Certificate or ServerCertificate - - N/A - - The path to a certificate file to match against the SQL Server TLS/SSL certificate. The accepted certificate formats are PEM, DER, and CER. If specified, the SQL Server certificate is checked by verifying if the ServerCertificate provided is an exact match. (Only available in v5.1+) - - - - - Initial Catalog or Database - - N/A - - - The name of the database. - - - The database name can be 128 characters or fewer. - - - - - - Integrated Security or Trusted_Connection - - false - - - When false, User ID and Password are specified in the connection. When true, the current Windows account credentials are used for authentication. - - - Recognized values are true, false, yes, no, and sspi (strongly recommended), which is equivalent to true. - - - If User ID and Password are specified and Integrated Security is set to true, the User ID and Password will be ignored and Integrated Security will be used. - - - is a more secure way to specify credentials for a connection that uses SQL Server Authentication (Integrated Security=false). - - - - - - IP Address Preference or IPAddressPreference - - IPv4First - - - The IP address family preference when establishing TCP connections. If Transparent Network IP Resolution (in .NET Framework) or Multi Subnet Failover is set to true, this setting has no effect. Supported values include: - - - IPAddressPreference=IPv4First - IPAddressPreference=IPv6First - IPAddressPreference=UsePlatformDefault - - - - - - Load Balance Timeout or Connection Lifetime - - 0 - - - When a connection is returned to the pool, its creation time is compared with the current time, and the connection is destroyed if that time span (in seconds) exceeds the value specified by Connection Lifetime. This is useful in clustered configurations to force load balancing between a running server and a server just brought online. - - - A value of zero (0) causes pooled connections to have the maximum connection timeout. - - - - - Max Pool Size - 100 - - - The maximum number of connections that are allowed in the pool. - - - Valid values are greater than or equal to 1. Values that are less than Min Pool Size generate an error. - - - - - Min Pool Size - 0 - - - The minimum number of connections that are allowed in the pool. - - - Valid values are greater than or equal to 0. Zero (0) in this field means no minimum connections are initially opened. - - - Values that are greater than Max Pool Size generate an error. - - - - - - Multiple Active Result Sets or MultipleActiveResultSets - - false - - - When true, an application can maintain multiple active result sets (MARS). When false, an application must process or cancel all result sets from one batch before it can execute any other batch on that connection. - - - Recognized values are true and false. - - - For more information, see Multiple Active Result Sets (MARS). - - - - - - Multi Subnet Failover or MultiSubnetFailover - - false - - - Always specify multiSubnetFailover=True when connecting to the availability group listener of a SQL Server 2012 (or later) availability group or a SQL Server 2012 (or later) Failover Cluster Instance. multiSubnetFailover=True configures SqlClient to provide faster detection of and connection to the (currently) active server. Possible values are Yes and No, True and False or 1 and 0. For example: - - - MultiSubnetFailover=True - - - The default is False. For more information about SqlClient's support for Always On AGs, see SqlClient Support for High Availability, Disaster Recovery. - - - - - - Network Library or Network or Net - - N/A - - - The network library used to establish a connection to an instance of SQL Server. Supported values include: - - - dbnmpntw (Named Pipes) - dbmsrpcn (Multiprotocol, Windows RPC) - dbmsadsn (Apple Talk) - dbmsgnet (VIA) - dbmslpcn (Shared Memory) - dbmsspxn (IPX/SPX) - dbmssocn (TCP/IP) - Dbmsvinn (Banyan Vines) - - - The corresponding network DLL must be installed on the system to which you connect. If you do not specify a network and you use a local server (for example, "." or "(local)"), shared memory is used. In this example, the network library is Win32 Winsock TCP/IP (dbmssocn), and 1433 is the port being used. - - - Network Library=dbmssocn;Data Source=000.000.000.000,1433; - - - - - Packet Size - 8000 - - - Size in bytes of the network packets used to communicate with an instance of SQL Server. - - - The packet size can be greater than or equal to 512 and less than or equal to 32768. - - - - - - Password or PWD - - N/A - - - The password for the SQL Server account logging on. Not recommended. To maintain a high level of security, we strongly recommend that you use the Integrated Security or Trusted_Connection keyword instead. is a more secure way to specify credentials for a connection that uses SQL Server Authentication. - - - The password must be 128 characters or fewer. - - - - - - Persist Security Info or PersistSecurityInfo - - false - - - When set to false or no (strongly recommended), security-sensitive information, such as the password or access token, is not returned as part of the connection if the connection is open or has ever been in an open state. This property should only be set to true if your application has a specific need to read the password out of an already-opened database connection. The default value of false is the more secure setting; using true for this property opens your application to security risks such as accidentally logging or tracing the database password. - - - Resetting the connection string resets all connection string values including the password. - - - Recognized values are true, false, yes, and no. - - - - - - Pool Blocking Period or PoolBlockingPeriod - - Auto - - Sets the blocking period behavior for a connection pool. See property for details. - - - - Pooling - true - - - When the value of this key is set to true, any newly created connection will be added to the pool when closed by the application. In a next attempt to open the same connection, that connection will be drawn from the pool. - - - Connections are considered the same if they have the same connection string. Different connections have different connection strings. - - - The value of this key can be true, false, yes, and no. - - - - - Replication - false - true if replication is supported using the connection. - - - - Server SPN or ServerSPN - - N/A - - - The SPN for the data source. The default value is an empty string, which causes SqlClient to use the default, driver-generated SPN. (Only available in v5.0+) - - - - - Transaction Binding - Implicit Unbind - - - Controls connection association with an enlisted System.Transactions transaction. - - - Possible values are: - - - Transaction Binding=Implicit Unbind; - Transaction Binding=Explicit Unbind; - - - Implicit Unbind causes the connection to detach from the transaction when it ends. After detaching, additional requests on the connection are performed in autocommit mode. The System.Transactions.Transaction.Current property is not checked when executing requests while the transaction is active. After the transaction has ended, additional requests are performed in autocommit mode. - - - If the system ends the transaction (in the scope of a using block) before the last command completes, it will throw . - - - Explicit Unbind causes the connection to remain attached to the transaction until the connection is closed or an explicit SqlConnection.TransactionEnlist(null) is called. Beginning in .NET Framework 4.0, changes to Implicit Unbind make Explicit Unbind obsolete. An InvalidOperationException is thrown if Transaction.Current is not the enlisted transaction or if the enlisted transaction is not active. - - - - - - Transparent Network IP Resolution or TransparentNetworkIPResolution - - See description. - - - When the value of this key is set to true, the application is required to retrieve all IP addresses for a particular DNS entry and attempt to connect with the first one in the list. If the connection is not established within 0.5 seconds, the application will try to connect to all others in parallel. When the first answers, the application will establish the connection with the respondent IP address. - - - If the MultiSubnetFailover key is set to true, TransparentNetworkIPResolution is ignored. - - - If the Failover Partner key is set, TransparentNetworkIPResolution is ignored. - - - The value of this key must be true, false, yes, or no. - - - A value of yes is treated the same as a value of true. - - - A value of no is treated the same as a value of false. - - - The default values are as follows: - - - - false when: - - - Connecting to Azure SQL Database where the data source ends with: - - .database.chinacloudapi.cn - .database.usgovcloudapi.net - .database.cloudapi.de - .database.windows.net - - - Authentication is 'Active Directory Password' or 'Active Directory Integrated' - - - true in all other cases. - - - - - - Trust Server Certificate or TrustServerCertificate - - false - - When set to true, TLS is used to encrypt the channel when bypassing walking the certificate chain to validate trust. If TrustServerCertificate is set to true and Encrypt is set to false, the channel is not encrypted. Recognized values are true, false, yes, and no. For more information, see Connection String Syntax. - - - - Type System Version - N/A - - - A string value that indicates the type system the application expects. The functionality available to a client application is dependent on the version of SQL Server and the compatibility level of the database. Explicitly setting the type system version that the client application was written for avoids potential problems that could cause an application to break if a different version of SQL Server is used. Note: The type system version cannot be set for common language runtime (CLR) code executing in-process in SQL Server. For more information, see SQL Server Common Language Runtime Integration. - - - Possible values are: - - - Type System Version=SQL Server 2012; - Type System Version=SQL Server 2008; - Type System Version=SQL Server 2005; - Type System Version=Latest; - - - Type System Version=SQL Server 2012; specifies that the application will require version 11.0.0.0 of Microsoft.SqlServer.Types.dll. The other Type System Version settings will require version 10.0.0.0 of Microsoft.SqlServer.Types.dll. - - - Latest is obsolete and should not be used. Latest is equivalent to Type System Version=SQL Server 2008;. - - - - - - User ID or UID or User - - N/A - - - The SQL Server login account. Not recommended. To maintain a high level of security, we strongly recommend that you use the Integrated Security or Trusted_Connection keywords instead. is a more secure way to specify credentials for a connection that uses SQL Server Authentication. - - - The user ID must be 128 characters or fewer. - - - - - User Instance - false - - A value that indicates whether to redirect the connection from the default SQL Server Express instance to a runtime-initiated instance running under the account of the caller. - - - - - Workstation ID or WSID - - The local computer name - - The name of the workstation connecting to SQL Server. - The ID must be 128 characters or fewer. - - -
    - - The following list contains the valid names for connection pooling values within the . For more information, see SQL Server Connection Pooling (ADO.NET). - - - Connection Lifetime (or Load Balance Timeout) - Enlist - Max Pool Size - Min Pool Size - Pooling - - - When you are setting keyword or connection pooling values that require a Boolean value, you can use yed instead of true, and no instead of false. Integer values are represented as strings. - - - The .NET Framework Data Provider for SQL Server uses its own protocol to communicate with SQL Server. Therefore, it does not support the use of an ODBC data source name (DSN) when connecting to SQL Server because it does not add an ODBC layer. - - - Universal data link (UDL) files are not supported for the .NET Framework Data Provider for SQL Server. - - - In this release, the application should use caution when constructing a connection string based on user input (for example when retrieving user ID and password information from a dialog box, and appending it to the connection string). The application should make sure that a user cannot embed additional connection string parameters in these values (for example, entering a password as "validpassword;database=somedb" in an attempt to attach to a different database). If you need to construct connection strings based on user input, use the new , which validates the connection string and helps to eliminate this problem. See Connection String Builders for more information. - + + + + Gets the default wait time (in seconds) before terminating the attempt to execute a command and generating an error. The default is 30 seconds. + + + The time in seconds to wait for the command to execute. The default is 30 seconds. + + + You can set the default wait time by using the Command Timeout keyword in the connection string. A value of 0 indicates no limit (an attempt to execute a command will wait indefinitely). + + + + + Gets or sets the string used to open a SQL Server database. + + + The connection string that includes the source database name, and other parameters needed to establish the initial connection. The default value is an empty string. + + + is similar to an OLE DB connection string, but is not identical. Unlike OLE DB or ADO, the connection string that is returned is the same as the user-set , minus security information if the Persist Security Info value is set to `false` (default). The .NET Framework Data Provider for SQL Server does not persist or return the password in a connection string unless you set Persist Security Info to `true`. +You can use the property to connect to a database. The following example illustrates a typical connection string. + +``` +"Persist Security Info=False;Integrated Security=true;Initial Catalog=Northwind;server=(local)" +``` + +Use the new to construct valid connection strings at run time. For more information, see [Connection String Builders](https://learn.microsoft.com/sql/connect/ado-net/connection-string-builders). + +The property can be set only when the connection is closed. Many of the connection string values have corresponding read-only properties. When the connection string is set, these properties are updated, except when an error is detected. In this case, none of the properties are updated. properties return only those settings that are contained in the . + +To connect to a local computer, specify "(local)" for the server. If a server name is not specified, a connection will be attempted to the default instance on the local computer. + +Resetting the on a closed connection resets all connection string values (and related properties) including the password. For example, if you set a connection string that includes "Database= AdventureWorks", and then reset the connection string to "Data Source=myserver;Integrated Security=true", the property is no longer set to "AdventureWorks". + +The connection string is parsed immediately after being set. If errors in syntax are found when parsing, a runtime exception, such as , is generated. Other errors can be found only when an attempt is made to open the connection. + +The basic format of a connection string includes a series of keyword/value pairs separated by semicolons. The equal sign (=) connects each keyword and its value. To include values that contain a semicolon, single-quote character, or double-quote character, the value must be enclosed in double quotation marks. If the value contains both a semicolon and a double-quote character, the value can be enclosed in single quotation marks. The single quotation mark is also useful if the value starts with a double-quote character. Conversely, the double quotation mark can be used if the value starts with a single quotation mark. If the value contains both single-quote and double-quote characters, the quotation mark character used to enclose the value must be doubled every time it occurs within the value. + +To include preceding or trailing spaces in the string value, the value must be enclosed in either single quotation marks or double quotation marks. Any leading or trailing spaces around integer, Boolean, or enumerated values are ignored, even if enclosed in quotation marks. However, spaces within a string literal keyword or value are preserved. Single or double quotation marks may be used within a connection string without using delimiters (for example, Data Source= my'Server or Data Source= my"Server), unless a quotation mark character is the first or last character in the value. + +Keywords are not case sensitive. + +The following table lists the valid names for keyword values within the . + +|Keyword|Default|Description| +|-------------|-------------|-----------------| +|Addr|N/A|Synonym of **Data Source**.| +|Address|N/A|Synonym of **Data Source**.| +|App|N/A|Synonym of **Application Name**.| +|Application Intent

    -or-

    ApplicationIntent|ReadWrite|Declares the application workload type when connecting to a server. Possible values are `ReadOnly` and `ReadWrite`. For example:

    `ApplicationIntent=ReadOnly`

    For more information about SqlClient support for Always On Availability Groups, see [SqlClient Support for High Availability, Disaster Recovery](https://learn.microsoft.com/sql/connect/ado-net/sql/sqlclient-support-high-availability-disaster-recovery).| +|Application Name|N/A|The name of the application. If no application name is provided, 'Framework Microsoft SqlClient Data Provider' when running on .NET Framework and 'Core Microsoft SqlClient Data Provider' otherwise.

    An application name can be 128 characters or less.| +|AttachDBFilename

    -or-

    Extended Properties

    -or-

    Initial File Name|N/A|The name of the primary database file, including the full path name of an attachable database. AttachDBFilename is only supported for primary data files with an .mdf extension.

    If the value of the AttachDBFileName key is specified in the connection string, the database is attached and becomes the default database for the connection.

    If this key is not specified and if the database was previously attached, the database will not be reattached. The previously attached database will be used as the default database for the connection.

    If this key is specified together with the AttachDBFileName key, the value of this key will be used as the alias. However, if the name is already used in another attached database, the connection will fail.

    The path may be absolute or relative by using the DataDirectory substitution string. If DataDirectory is used, the database file must exist within a subdirectory of the directory pointed to by the substitution string. **Note:** Remote server, HTTP, and UNC path names are not supported.

    The database name must be specified with the keyword 'database' (or one of its aliases) as in the following:

    "AttachDbFileName=|DataDirectory|\data\YourDB.mdf;integrated security=true;database=YourDatabase"

    An error will be generated if a log file exists in the same directory as the data file and the 'database' keyword is used when attaching the primary data file. In this case, remove the log file. Once the database is attached, a new log file will be automatically generated based on the physical path.| +|Attestation Protocol|NotSpecified|Gets or sets the value of Attestation Protocol.

    When no value is specified, secure enclaves are disabled on the connection.

    Valid values are:
    `AAS`
    `HGS`
    `None` (Only valid in v3.1 and v4.1+))| +|Authentication|N/A|The authentication method used for [Connecting to SQL Database By Using Azure Active Directory Authentication](https://azure.microsoft.com/documentation/articles/sql-database-aad-authentication/#7-connect-to-your-database-by-using-azure-active-directory-identities).

    Valid values are:

    `Active Directory Integrated`, `Active Directory Interactive`, `Active Directory Password`, `Active Directory Service Principal`, `Active Directory Device Code Flow`, `Active Directory Managed Identity`, `Active Directory MSI`, `Active Directory Default`, `Sql Password`.

    For additional information see [Using Azure Active Directory authentication with SqlClient](https://docs.microsoft.com/sql/connect/ado-net/sql/azure-active-directory-authentication?view=sql-server-ver15).| +|Column Encryption Setting|disabled|Enables or disables [Always Encrypted](https://learn.microsoft.com/sql/relational-databases/security/encryption/always-encrypted-database-engine) functionality for the connection. Supported values are: `enabled` and `disabled`| +|Command Timeout|30|The default wait time (in seconds) before terminating the attempt to execute a command and generating an error.

    Valid values are greater than or equal to 0 and less than or equal to 2147483647.| +|Connect Retry Count

    -or-

    ConnectRetryCount|1|Controls the number of reconnection attempts after the client identifies an idle connection failure. Valid values are 0 to 255. 0 means do not attempt to reconnect (disable connection resiliency).

    Note: Since version 5.x the default value for non Azure endpoints is 1. For Azure SQL endpoints, the default is 2. For Azure SQL serverless or on demand endpoints, the default is 5 to improve connection success for connections to an idle or paused instance.

    For additional information about idle connection resiliency, see [.NET SqlConnection parameters for connection retry](https://learn.microsoft.com/azure/azure-sql/database/troubleshoot-common-connectivity-issues?view=azuresql#net-sqlconnection-parameters-for-connection-retry) and [Technical Article - Idle Connection Resiliency](https://go.microsoft.com/fwlink/?LinkId=393996).| +|Connect Retry Interval

    -or-

    ConnectRetryInterval|10|Specifies the time between each connection retry attempt (`ConnectRetryCount`). Valid values are 1 to 60 seconds (default=10), applied after the first reconnection attempt. When a broken connection is detected, the client immediately attempts to reconnect; this is the first reconnection attempt and only occurs if `ConnectRetryCount` is greater than 0. If the first reconnection attempt fails and `ConnectRetryCount` is greater than 1, the client waits `ConnectRetryInterval` to try the second and subsequent reconnection attempts.

    For additional information about idle connection resiliency, see[.NET SqlConnection parameters for connection retry](https://learn.microsoft.com/azure/azure-sql/database/troubleshoot-common-connectivity-issues?view=azuresql#net-sqlconnection-parameters-for-connection-retry) and [Technical Article - Idle Connection Resiliency](https://go.microsoft.com/fwlink/?LinkId=393996).| +|Connect Timeout

    -or-

    Connection Timeout

    -or-

    Timeout|15|The length of time (in seconds) to wait for a connection to the server before terminating the attempt and generating an error.

    Valid values are greater than or equal to 0 and less than or equal to 2147483647.

    When opening a connection to a Azure SQL Database, set the connection timeout to 30 seconds.| +|Current Language

    -or-

    Language|N/A|Sets the language used for database server warning or error messages.

    The language name can be 128 characters or less.| +|Data Source

    -or-

    Server

    -or-

    Address

    -or-

    Addr

    -or-

    Network Address|N/A|The name or network address of the instance of SQL Server to which to connect. The port number can be specified after the server name:

    `server=tcp:servername, portnumber`

    When specifying a local instance, always use (local). To force a protocol, add one of the following prefixes:

    `np:(local), tcp:(local), lpc:(local)`

    You can also connect to a LocalDB database as follows:

    `server=(localdb)\\myInstance`

    For more information about LocalDB, see [SqlClient Support for LocalDB](https://learn.microsoft.com/sql/connect/ado-net/sql/sqlclient-support-localdb).

    **Data Source** must use the TCP format or the Named Pipes format.

    TCP format is as follows:

    - tcp:\\\
    - tcp:\,\

    The TCP format must start with the prefix "tcp:" and is followed by the database instance, as specified by a host name and an instance name. This format is not applicable when connecting to Azure SQL Database. TCP is automatically selected for connections to Azure SQL Database when no protocol is specified.

    The host name MUST be specified in one of the following ways:

    - NetBIOSName
    - IPv4Address
    - IPv6Address

    The instance name is used to resolve to a particular TCP/IP port number on which a database instance is hosted. Alternatively, specifying a TCP/IP port number directly is also allowed. If both instance name and port number are not present, the default database instance is used.

    The Named Pipes format is as follows:

    - np:\\\\\pipe\\

    The Named Pipes format MUST start with the prefix "np:" and is followed by a named pipe name.

    The host name MUST be specified in one of the following ways:

    - NetBIOSName
    - IPv4Address
    - IPv6Address

    The pipe name is used to identify the database instance to which the .NET application will connect.

    If the value of the **Network** key is specified, the prefixes "tcp:" and "np:" should not be specified. **Note:** You can force the use of TCP instead of shared memory, either by prefixing **tcp:** to the server name in the connection string, or by using **localhost**.| +|Enclave Attestation Url|N/A|Gets or sets the enclave attestation URL to be used with enclave based Always Encrypted.| +|Encrypt|'true' in 4.0 and above

    'false' in 3.x and below|Recognized values are:
    versions 1 - 4: `true`/`yes` and `false`/`no`
    versions 5+: `true`/`yes`/`mandatory`, `false`/`no`/`optional` and `strict`. When `true`, TLS encryption is used for all data sent between the client and server if the server has a certificate installed. When `strict`, TDS 8.0 TLS encryption is used and the `TrustServerCertificate` setting is ignored and treated as false. For more information, see [Connection String Syntax](https://learn.microsoft.com/sql/connect/ado-net/connection-string-syntax).

    When `Encrypt` is `mandatory` or `strict` and `TrustServerCertificate` is `false`, the server name (or IP address) in a server's certificate must exactly match the server name (or IP address) specified in the connection string. Otherwise, the connection attempt will fail. | +|Enlist|'true'|`true` indicates that the SQL Server connection pooler automatically enlists the connection in the creation thread's current transaction context.| +|Failover Partner|N/A|The name of the failover partner server where database mirroring is configured.

    If the value of this key is "", then **Initial Catalog** must be present, and its value must not be "".

    The server name can be 128 characters or less.

    If you specify a failover partner but the failover partner server is not configured for database mirroring and the primary server (specified with the Server keyword) is not available, then the connection will fail.

    If you specify a failover partner and the primary server is not configured for database mirroring, the connection to the primary server (specified with the Server keyword) will succeed if the primary server is available.| +|Failover Partner SPN

    -or-

    FailoverPartnerSPN|N/A|The SPN for the failover partner. The default value is an empty string, which causes SqlClient to use the default, driver-generated SPN.

    (Only available in v5.0+)| +|Host Name In Certificate

    -or-

    HostNameInCertificate|N/A|The host name to use when validating the server certificate. When not specified, the server name from the Data Source is used for certificate validation.

    (Only available in v5.0+)| +|Server Certificate

    -or-

    ServerCertificate|N/A|The path to a certificate file to match against the SQL Server TLS/SSL certificate. The accepted certificate formats are PEM, DER, and CER. If specified, the SQL Server certificate is checked by verifying if the ServerCertificate provided is an exact match.

    (Only available in v5.1+)| +|Initial Catalog

    -or-

    Database|N/A|The name of the database.

    The database name can be 128 characters or less.| +|Integrated Security

    -or-

    Trusted_Connection|'false'|When `false`, User ID and Password are specified in the connection. When `true`, the current Windows account credentials are used for authentication.

    Recognized values are `true`, `false`, `yes`, `no`, and `sspi` (strongly recommended), which is equivalent to `true`.

    If User ID and Password are specified and Integrated Security is set to true, the User ID and Password will be ignored and Integrated Security will be used.

    is a more secure way to specify credentials for a connection that uses SQL Server Authentication (`Integrated Security=false`).| +|IP Address Preference

    -or-

    IPAddressPreference|IPv4First|The IP address family preference when establishing TCP connections. If `Transparent Network IP Resolution` (in .NET Framework) or `Multi Subnet Failover` is set to true, this setting has no effect. Supported values include:

    `IPAddressPreference=IPv4First`

    `IPAddressPreference=IPv6First`

    `IPAddressPreference=UsePlatformDefault`| +|Load Balance Timeout

    -or-

    Connection Lifetime|0|When a connection is returned to the pool, its creation time is compared with the current time, and the connection is destroyed if that time span (in seconds) exceeds the value specified by `Connection Lifetime`. This is useful in clustered configurations to force load balancing between a running server and a server just brought online.

    A value of zero (0) causes pooled connections to have the maximum connection timeout.| +|Max Pool Size|100|The maximum number of connections that are allowed in the pool.

    Valid values are greater than or equal to 1. Values that are less than **Min Pool Size** generate an error.| +|Min Pool Size|0|The minimum number of connections that are allowed in the pool.

    Valid values are greater than or equal to 0. Zero (0) in this field means no minimum connections are initially opened.

    Values that are greater than **Max Pool Size** generate an error.| +|Multiple Active Result Sets

    -or-

    MultipleActiveResultSets|false|When `true`, an application can maintain multiple active result sets (MARS). When `false`, an application must process or cancel all result sets from one batch before it can execute any other batch on that connection.

    Recognized values are `true` and `false`.

    For more information, see [Multiple Active Result Sets (MARS)](https://learn.microsoft.com/sql/connect/ado-net/sql/multiple-active-result-sets-mars).| +|Multi Subnet Failover

    -or-

    MultiSubnetFailover|false|Always specify `multiSubnetFailover=True` when connecting to the availability group listener of a SQL Server 2012 (or later) availability group or a SQL Server 2012 (or later) Failover Cluster Instance. `multiSubnetFailover=True` configures SqlClient to provide faster detection of and connection to the (currently) active server. Possible values are `Yes` and `No`, `True` and `False` or `1` and `0`. For example:

    `MultiSubnetFailover=True`

    The default is `False`. For more information about SqlClient's support for Always On AGs, see [SqlClient Support for High Availability, Disaster Recovery](https://learn.microsoft.com/sql/connect/ado-net/sql/sqlclient-support-high-availability-disaster-recovery).| +|Network Library

    -or-

    Network

    -or-

    Net|N/A|The network library used to establish a connection to an instance of SQL Server. Supported values include:

    dbnmpntw (Named Pipes)

    dbmsrpcn (Multiprotocol, Windows RPC)

    dbmsadsn (Apple Talk)

    dbmsgnet (VIA)

    dbmslpcn (Shared Memory)

    dbmsspxn (IPX/SPX)

    dbmssocn (TCP/IP)

    Dbmsvinn (Banyan Vines)

    The corresponding network DLL must be installed on the system to which you connect. If you do not specify a network and you use a local server (for example, "." or "(local)"), shared memory is used. In this example, the network library is Win32 Winsock TCP/IP (dbmssocn), and 1433 is the port being used.

    `Network Library=dbmssocn;Data Source=000.000.000.000,1433;`| +|Packet Size|8000|Size in bytes of the network packets used to communicate with an instance of SQL Server.

    The packet size can be greater than or equal to 512 and less than or equal to 32768.| +|Password

    -or-

    PWD|N/A|The password for the SQL Server account logging on. Not recommended. To maintain a high level of security, we strongly recommend that you use the `Integrated Security` or `Trusted_Connection` keyword instead. is a more secure way to specify credentials for a connection that uses SQL Server Authentication.

    The password must be 128 characters or less.| +|Persist Security Info

    -or-

    PersistSecurityInfo|'false'|When set to `false` or `no` (strongly recommended), security-sensitive information, such as the password or access token, is not returned as part of the connection if the connection is open or has ever been in an open state. This property should only be set to `true` if your application has a specific need to read the password out of an already-opened database connection. The default value of `false` is the more secure setting; using `true` for this property opens your application to security risks such as accidentally logging or tracing the database password.

    Resetting the connection string resets all connection string values including the password. Recognized values are `true`, `false`, `yes`, and `no`.| +|Pool Blocking Period

    -or-

    PoolBlockingPeriod|Auto|Sets the blocking period behavior for a connection pool. See property for details.| +|Pooling|'true'|When the value of this key is set to true, any newly created connection will be added to the pool when closed by the application. In a next attempt to open the same connection, that connection will be drawn from the pool.

    Connections are considered the same if they have the same connection string. Different connections have different connection strings.

    The value of this key can be "true", "false", "yes", or "no".| +|Replication|'false'|`true` if replication is supported using the connection.| +|Server SPN

    -or-

    ServerSPN|N/A|The SPN for the data source. The default value is an empty string, which causes SqlClient to use the default, driver-generated SPN.

    (Only available in v5.0+)| +|Transaction Binding|Implicit Unbind|Controls connection association with an enlisted `System.Transactions` transaction.

    Possible values are:

    `Transaction Binding=Implicit Unbind;`

    `Transaction Binding=Explicit Unbind;`

    Implicit Unbind causes the connection to detach from the transaction when it ends. After detaching, additional requests on the connection are performed in autocommit mode. The `System.Transactions.Transaction.Current` property is not checked when executing requests while the transaction is active. After the transaction has ended, additional requests are performed in autocommit mode.

    If the system ends the transaction (in the scope of a using block) before the last command completes, it will throw .

    Explicit Unbind causes the connection to remain attached to the transaction until the connection is closed or an explicit `SqlConnection.TransactionEnlist(null)` is called. Beginning in .NET Framework 4.0, changes to Implicit Unbind make Explicit Unbind obsolete. An `InvalidOperationException` is thrown if `Transaction.Current` is not the enlisted transaction or if the enlisted transaction is not active.| +|Transparent Network IP Resolution

    -or-

    TransparentNetworkIPResolution|See description.|When the value of this key is set to `true`, the application is required to retrieve all IP addresses for a particular DNS entry and attempt to connect with the first one in the list. If the connection is not established within 0.5 seconds, the application will try to connect to all others in parallel. When the first answers, the application will establish the connection with the respondent IP address.

    If the `MultiSubnetFailover` key is set to `true`, `TransparentNetworkIPResolution` is ignored.

    If the `Failover Partner` key is set, `TransparentNetworkIPResolution` is ignored.

    The value of this key must be `true`, `false`, `yes`, or `no`.

    A value of `yes` is treated the same as a value of `true`.

    A value of `no` is treated the same as a value of `false`.

    The default values are as follows:

    • `false` when:

      • Connecting to Azure SQL Database where the data source ends with:

        • .database.chinacloudapi.cn
        • .database.usgovcloudapi.net
        • .database.cloudapi.de
        • .database.windows.net
        • .database.fabric.microsoft.com
      • `Authentication` is 'Active Directory Password' or 'Active Directory Integrated'
    • `true` in all other cases.
    | +|Trust Server Certificate

    -or-

    TrustServerCertificate|'false'|When set to `true`, TLS is used to encrypt the channel when bypassing walking the certificate chain to validate trust. If TrustServerCertificate is set to `true` and Encrypt is set to `false`, the channel is not encrypted. Recognized values are `true`, `false`, `yes`, and `no`. For more information, see [Connection String Syntax](https://learn.microsoft.com/sql/connect/ado-net/connection-string-syntax).| +|Type System Version|N/A|A string value that indicates the type system the application expects. The functionality available to a client application is dependent on the version of SQL Server and the compatibility level of the database. Explicitly setting the type system version that the client application was written for avoids potential problems that could cause an application to break if a different version of SQL Server is used. **Note:** The type system version cannot be set for common language runtime (CLR) code executing in-process in SQL Server. For more information, see [SQL Server Common Language Runtime Integration](https://learn.microsoft.com/dotnet/framework/data/adonet/sql/sql-server-common-language-runtime-integration).

    Possible values are:

    `Type System Version=SQL Server 2012;`

    `Type System Version=SQL Server 2008;`

    `Type System Version=SQL Server 2005;`

    `Type System Version=Latest;`

    `Type System Version=SQL Server 2012;` specifies that the application will require version 11.0.0.0 of Microsoft.SqlServer.Types.dll. The other `Type System Version` settings will require version 10.0.0.0 of Microsoft.SqlServer.Types.dll.

    `Latest` is obsolete and should not be used. `Latest` is equivalent to `Type System Version=SQL Server 2008;`.| +|User ID

    -or-

    UID

    -or-

    User|N/A|The SQL Server login account. Not recommended. To maintain a high level of security, we strongly recommend that you use the `Integrated Security` or `Trusted_Connection` keywords instead. is a more secure way to specify credentials for a connection that uses SQL Server Authentication.

    The user ID must be 128 characters or less.| +|User Instance|'false'|A value that indicates whether to redirect the connection from the default SQL Server Express instance to a runtime-initiated instance running under the account of the caller.| +|Workstation ID

    -or-

    WSID|The local computer name|The name of the workstation connecting to SQL Server.

    The ID must be 128 characters or less.| + +The following list contains the valid names for connection pooling values within the . For more information, see [SQL Server Connection Pooling (ADO.NET)](https://learn.microsoft.com/sql/connect/ado-net/sql-server-connection-pooling). + +- Connection Lifetime (or Load Balance Timeout) + +- Enlist + +- Max Pool Size + +- Min Pool Size + +- Pooling + +When you are setting keyword or connection pooling values that require a Boolean value, you can use 'yes' instead of 'true', and 'no' instead of 'false'. Integer values are represented as strings. +> [!NOTE] +> The .NET Framework Data Provider for SQL Server uses its own protocol to communicate with SQL Server. Therefore, it does not support the use of an ODBC data source name (DSN) when connecting to SQL Server because it does not add an ODBC layer. + +> [!NOTE] +> Universal data link (UDL) files are not supported for the .NET Framework Data Provider for SQL Server. + +> [!CAUTION] +> In this release, the application should use caution when constructing a connection string based on user input (for example when retrieving user ID and password information from a dialog box, and appending it to the connection string). The application should make sure that a user cannot embed additional connection string parameters in these values (for example, entering a password as "validpassword;database=somedb" in an attempt to attach to a different database). If you need to construct connection strings based on user input, use the new , which validates the connection string and helps to eliminate this problem. See [Connection String Builders](https://learn.microsoft.com/sql/connect/ado-net/connection-string-builders) for more information. + +## Examples +The following example creates a and sets the property before opening the connection. + +[!code-csharp[SqlConnection_ConnectionString Example#1](~/../sqlclient/doc/samples/SqlConnection_ConnectionString.cs#1)] + +]]>
    - - - The following example creates a and sets the property before opening the connection. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - OpenSqlConnection(); - Console.ReadLine(); - } - - private static void OpenSqlConnection() - { - string connectionString = GetConnectionString(); - - using (SqlConnection connection = new SqlConnection()) - { - connection.ConnectionString = connectionString; - - connection.Open(); - - Console.WriteLine("State: {0}", connection.State); - Console.WriteLine("ConnectionString: {0}", - connection.ConnectionString); - } - } - - static private string GetConnectionString() - { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. - return "Data Source=MSSQL1;Initial Catalog=AdventureWorks;" - + "Integrated Security=true;"; - } - } - - An invalid connection string argument has been supplied, or a required connection string argument has not been supplied. @@ -2133,18 +1163,18 @@ Persist Security Info = true is required to get the value of the object with . The default value of is null. An exception will be raised: - + If is set on an open connection. - - + + If is set when Integrated Security = true. - - + + If is set when the connection string uses Password. - - + + If is set when the connection string uses UserID. - +
    @@ -2313,15 +1343,16 @@ if the property has been set; otherwise . - - When you set to true, errors that were previously treated as exceptions are now handled as events. All events fire immediately and are handled by the event handler. If is is set to false, then events are handled at the end of the procedure. - - - An error with a severity level of 17 or above that causes the server to stop processing the command needs to be handled as an exception. In this case, an exception is thrown regardless of how the error is handled in the event. - - - For more information on working with events, see Connection Events. For more information on errors generated by the SQL Server engine, see Database Engine Errors. - + to `true`, errors that were previously treated as exceptions are now handled as events. All events fire immediately and are handled by the event handler. If is is set to `false`, then events are handled at the end of the procedure. + +> [!NOTE] +> An error with a severity level of 17 or above that causes the server to stop processing the command needs to be handled as an exception. In this case, an exception is thrown regardless of how the error is handled in the event. + +For more information on working with events, see [Connection Events](https://learn.microsoft.com/sql/connect/ado-net/connection-events). For more information on errors generated by the SQL Server engine, see [Database Engine Errors](https://learn.microsoft.com/sql/relational-databases/errors-events/database-engine-events-and-errors). + +]]> @@ -2347,9 +1378,9 @@ You may need the schema information of the database, tables or columns. This sample: - Uses GetSchema to get schema information. - Use schema restrictions to get the specified information. - Gets schema information of the database, tables, and some columns. + Uses GetSchema to get schema information. + Use schema restrictions to get the specified information. + Gets schema information of the database, tables, and some columns. @@ -2676,64 +1707,30 @@ Opens a database connection with the property settings specified by the . - - The draws an open connection from the connection pool if one is available. Otherwise, it establishes a new connection to an instance of SQL Server. - - - If the goes out of scope, it is not closed. Therefore, you must explicitly close the connection by calling . - - - If you specify a port number other than 1433 when you are trying to connect to an instance of SQL Server and using a protocol other than TCP/IP, the method fails. To specify a port number other than 1433, include "server=machinename,port number" in the connection string, and use the TCP/IP protocol. - - - The .NET Framework Data Provider for SQL Server requires the Security permission with "Allows calls to unmanaged assemblies" enabled ( with set to UnmanagedCode) to open a with SQL Debugging enabled. - + draws an open connection from the connection pool if one is available. Otherwise, it establishes a new connection to an instance of SQL Server. + +> [!NOTE] +> If the goes out of scope, it is not closed. Therefore, you must explicitly close the connection by calling . + +> [!NOTE] +> If you specify a port number other than 1433 when you are trying to connect to an instance of SQL Server and using a protocol other than TCP/IP, the method fails. To specify a port number other than 1433, include "server=machinename,port number" in the connection string, and use the TCP/IP protocol. + +> [!NOTE] +> The .NET Framework Data Provider for SQL Server requires the Security permission with "Allows calls to unmanaged assemblies" enabled ( with set to `UnmanagedCode`) to open a with SQL Debugging enabled. + +## Examples +The following example creates a , opens it, and displays some of its properties. The connection is automatically closed at the end of the `using` block. + +[!code-csharp[SqlConnection_Open Example#1](~/../sqlclient/doc/samples/SqlConnection_Open.cs#1)] + +]]> - - - The following example creates a , opens it, and displays some of its properties. The connection is automatically closed at the end of the using block. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program1 - { - static void Main() - { - string s = GetConnectionString(); - - OpenSqlConnection(s); - Console.ReadLine(); - } - - private static void OpenSqlConnection(string connectionString) - { - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - Console.WriteLine("ServerVersion: {0}", connection.ServerVersion); - Console.WriteLine("State: {0}", connection.State); - } - } - - static private string GetConnectionString() - { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file, using the - // System.Configuration.ConfigurationSettings.AppSettings property - return "Data Source=(local);Initial Catalog=AdventureWorks;" - + "Integrated Security=SSPI;"; - } - } - - - Cannot open a connection without specifying a data source or server. - The connection is already open. + Cannot open a connection without specifying a data source or server. + The connection is already open. @@ -2756,64 +1753,31 @@ Opens a database connection with the property settings specified by the . - - The draws an open connection from the connection pool if one is available. Otherwise, it establishes a new connection to an instance of SQL Server. If overrides are specified, the first open attempt will apply the specified overrides to the open action. - - - If the goes out of scope, it is not closed. Therefore, you must explicitly close the connection by calling . - - - If you specify a port number other than 1433 when you are trying to connect to an instance of SQL Server and using a protocol other than TCP/IP, the method fails. To specify a port number other than 1433, include "server=machinename,port number" in the connection string, and use the TCP/IP protocol. - - - The .NET Framework Data Provider for SQL Server requires the Security permission with "Allows calls to unmanaged assemblies" enabled ( with set to UnmanagedCode) to open a with SQL Debugging enabled. - + draws an open connection from the connection pool if one is available. Otherwise, it establishes a new connection to an instance of SQL Server. If overrides are specified, the first open attempt will apply the specified overrides to the open action. + +> [!NOTE] +> If the goes out of scope, it is not closed. Therefore, you must explicitly close the connection by calling . + +> [!NOTE] +> If you specify a port number other than 1433 when you are trying to connect to an instance of SQL Server and using a protocol other than TCP/IP, the method fails. To specify a port number other than 1433, include "server=machinename,port number" in the connection string, and use the TCP/IP protocol. + +> [!NOTE] +> The .NET Framework Data Provider for SQL Server requires the Security permission with "Allows calls to unmanaged assemblies" enabled ( with set to `UnmanagedCode`) to open a with SQL Debugging enabled. + +## Examples +The following example creates a , opens it, and displays some of its properties. The connection is automatically closed at the end of the `using` block. + +[!code-csharp[SqlConnection_Open Example#1](~/../sqlclient/doc/samples/SqlConnection_Open.cs#1)] + +]]> + - - - The following example creates a , opens it, and displays some of its properties. The connection is automatically closed at the end of the using block. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program1 - { - static void Main() - { - string s = GetConnectionString(); - - OpenSqlConnection(s); - Console.ReadLine(); - } - - private static void OpenSqlConnection(string connectionString) - { - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - Console.WriteLine("ServerVersion: {0}", connection.ServerVersion); - Console.WriteLine("State: {0}", connection.State); - } - } - - static private string GetConnectionString() - { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file, using the - // System.Configuration.ConfigurationSettings.AppSettings property - return "Data Source=(local);Initial Catalog=AdventureWorks;" - + "Integrated Security=SSPI;"; - } - } - - - Cannot open a connection without specifying a data source or server. - The connection is already open. + Cannot open a connection without specifying a data source or server. + The connection is already open. @@ -2868,15 +1832,15 @@ - After calling , must return until the returned is completed. Then, if the connection was successful, must return . If the connection fails, must return . + After calling , must return until the returned is completed. Then, if the connection was successful, must return . If the connection fails, must return . - A call to will attempt to cancel or close the corresponding call. For more information about asynchronous programming in the .NET Framework Data Provider for SQL Server, see Asynchronous Programming. + A call to will attempt to cancel or close the corresponding call. For more information about asynchronous programming in the .NET Framework Data Provider for SQL Server, see Asynchronous Programming. - Calling more than once for the same instance before task completion. + Calling more than once for the same instance before task completion. A connection was not available from the connection pool before the connection time out elapsed. @@ -2964,9 +1928,9 @@ - A null dictionary was provided. - A string key in the dictionary was null or empty. - A value in the dictionary was null. + A null dictionary was provided. + A string key in the dictionary was null or empty. + A value in the dictionary was null. @@ -2988,15 +1952,15 @@ Custom master key store providers can be registered with the driver at three different layers. The precedence of the three registrations is as follows: - + The per-command registration will be checked if it is not empty. - - + + If the per-command registration is empty, the per-connection registration will be checked if it is not empty. - - + + If the per-connection registration is empty, the global registration will be checked. - + Once any key store provider is found at a registration level, the driver will NOT fall back to the other registrations to search for a provider. If providers are registered but the proper provider is not found at a level, an exception will be thrown containing only the registered providers in the registration that was checked. @@ -3010,9 +1974,9 @@ - A null dictionary was provided. - A string key in the dictionary was null or empty. - A value in the dictionary was null. + A null dictionary was provided. + A string key in the dictionary was null or empty. + A value in the dictionary was null. @@ -3027,150 +1991,35 @@ When set to null (default), the default non-retryable provider will be applied. - - You must set the value for this property before opening the connection to take effect. - - - To apply the retry logic, do the following steps before opening the connection: - - - - Define the configuration parameters by using type. - - - Create a by using one of the following static methods of the class: - - - - - - - - - Assign the object to the RetryLogicProvider property. - - - - Detecting retryable exceptions is a vital part of the retry pattern. Before applying retry logic, it is important to investigate exceptions and choose a retry provider that best fits your scenario. First, log your exceptions and find transient faults. - - - The connection timeout restarts for each execution of a connection open. There is no timing overlap between these two actions. - - - The default retry logic provider is not enabled unless it is configured in an application configuration file. For more information, see Configurable retry logic and configuration file. - + type. +2. Create a by using one of the following static methods of the class: + - + - + - + - +3. Assign the object to the `RetryLogicProvider` property. + +> [!NOTE] +> Detecting retriable exceptions is a vital part of the retry pattern. Before applying retry logic, it is important to investigate exceptions and choose a retry provider that best fits your scenario. First, log your exceptions and find transient faults. + +> [!NOTE] +> The connection **timeout** restarts for each execution of a connection open. There is no timing overlap between these two actions. + +> [!NOTE] +> The default retry logic provider is not enabled unless it is configured in an application configuration file. For more information, see [Configurable retry logic and configuration file](https://learn.microsoft.com/sql/connect/ado-net/configurable-retry-logic-config-file-sqlclient). + +## Example +The following sample tries to open a connection to an invalid database to simulate a condition that the database service is temporarily unavailable . You should manually create the database while the tries to establish the connection. + +[!code-csharp[SqlConfigurableRetryLogic_OpenConnection#1](~/../sqlclient/doc/samples/SqlConfigurableRetryLogic_OpenConnection.cs#1)] + +]]> - - - The following sample tries to open a connection to an invalid database to simulate a condition that the database service is temporarily unavailable . You should manually create the database while the tries to establish the connection. - - - - using System; - using Microsoft.Data.SqlClient; - - /// Detecting retriable exceptions is a vital part of the retry pattern. - /// Before applying retry logic it is important to investigate exceptions and choose a retry provider that best fits your scenario. - /// First, log your exceptions and find transient faults. - /// The purpose of this sample is to illustrate how to use this feature and the condition might not be realistic. - class RetryLogicSample - { - private const string DefaultDB = "Northwind"; - private const string CnnStringFormat = "Server=localhost; Initial Catalog={0}; Integrated Security=true; pooling=false;"; - private const string DropDatabaseFormat = "DROP DATABASE {0}"; - - // For general use - private static SqlConnection s_generalConnection = new SqlConnection(string.Format(CnnStringFormat, DefaultDB)); - - static void Main(string[] args) - { - // 1. Define the retry logic parameters - var options = new SqlRetryLogicOption() - { - NumberOfTries = 5, - MaxTimeInterval = TimeSpan.FromSeconds(20), - DeltaTime = TimeSpan.FromSeconds(1) - }; - - // 2. Create a retry provider - var provider = SqlConfigurableRetryFactory.CreateExponentialRetryProvider(options); - - // define the retrying event to report the execution attempts - provider.Retrying += (object s, SqlRetryingEventArgs e) => - { - int attempts = e.RetryCount + 1; - Console.ForegroundColor = ConsoleColor.Yellow; - Console.WriteLine($"attempt {attempts} - current delay time:{e.Delay} \n"); - Console.ForegroundColor = ConsoleColor.DarkGray; - if (e.Exceptions[e.Exceptions.Count - 1] is SqlException ex) - { - Console.WriteLine($"{ex.Number}-{ex.Message}\n"); - } - else - { - Console.WriteLine($"{e.Exceptions[e.Exceptions.Count - 1].Message}\n"); - } - - // It is not a good practice to do time-consuming tasks inside the retrying event which blocks the running task. - // Use parallel programming patterns to mitigate it. - if (e.RetryCount == provider.RetryLogic.NumberOfTries - 1) - { - Console.WriteLine("This is the last chance to execute the command before throwing the exception."); - Console.WriteLine("Press Enter when you're ready:"); - Console.ReadLine(); - Console.WriteLine("continue ..."); - } - }; - - // Open the general connection. - s_generalConnection.Open(); - - try - { - // Assume the database is being created and other services are going to connect to it. - RetryConnection(provider); - } - catch - { - // exception is thrown if connecting to the database isn't successful. - throw; - } - } - - private static void ExecuteCommand(SqlConnection cn, string command) - { - using var cmd = cn.CreateCommand(); - cmd.CommandText = command; - cmd.ExecuteNonQuery(); - } - - private static void RetryConnection(SqlRetryLogicBaseProvider provider) - { - // Change this if you already have a database with the same name in your database. - string dbName = "Invalid_DB_Open"; - - // Create a connection to an invalid database. - using var cnn = new SqlConnection(string.Format(CnnStringFormat, dbName)); - // 3. Assign the `provider` to the connection - cnn.RetryLogicProvider = provider; - Console.WriteLine($"Connecting to the [{dbName}] ..."); - // Manually execute the following command in SSMS to create the invalid database while the SqlConnection is attempting to connect to it. - // >> CREATE DATABASE Invalid_DB_Open; - Console.WriteLine($"Manually, run the 'CREATE DATABASE {dbName};' in the SQL Server before exceeding the {provider.RetryLogic.NumberOfTries} attempts."); - // the connection tries to connect to the database 5 times - Console.WriteLine("The first attempt, before getting into the retry logic."); - cnn.Open(); - Console.WriteLine($"Connected to the [{dbName}] successfully."); - - cnn.Close(); - - // Drop it after test - ExecuteCommand(s_generalConnection, string.Format(DropDatabaseFormat, dbName)); - Console.WriteLine($"The [{dbName}] is removed."); - } - } - - @@ -3205,18 +2054,18 @@ Supported internal properties - Type - Information provided - Return value + Type + Information provided + Return value - SQLDNSCachingSupportedState + SQLDNSCachingSupportedState string To indicate the IsSupported flag sent by the server for DNS Caching "true", "false", "innerConnection is null!" - SQLDNSCachingSupportedStateBeforeRedirect + SQLDNSCachingSupportedStateBeforeRedirect string To indicate the IsSupported flag sent by the server for DNS Caching before redirection. "true", "false", "innerConnection is null!" diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml index 8d90d1b5b5..a68c0a323b 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlConnectionStringBuilder.xml @@ -5,101 +5,54 @@ Provides a simple way to create and manage the contents of connection strings used by the class. - - The connection string builder lets developers programmatically create syntactically correct connection strings, and parse and rebuild existing connection strings, using properties and methods of the class. The connection string builder provides strongly typed properties corresponding to the known key/value pairs allowed by SQL Server. Developers needing to create connection strings as part of applications can use the class to build and modify connection strings. The class also makes it easy to manage connection strings stored in an application configuration file. - - - The performs checks for valid key/value pairs. Therefore, you cannot use this class to create invalid connection strings; trying to add invalid pairs will throw an exception. The class maintains a fixed collection of synonyms and can translate from a synonym to the corresponding well-known key name. - - - For example, when you use the property to retrieve a value, you can specify a string that contains any synonym for the key you need. For example, you can specify "Network Address", "addr", or any other acceptable synonym for this key within a connection string when you use any member that requires a string that contains the key name, such as the property or the method. See the property for a full list of acceptable synonyms. - - - The property handles tries to insert malicious entries. For example, the following code, using the default Item property (the indexer, in C#) correctly escapes the nested key/value pair: - - - Dim builder As New Microsoft.Data.SqlClient.SqlConnectionStringBuilder - builder("Data Source") = "(local)" - builder("Integrated Security") = True - builder("Initial Catalog") = "AdventureWorks;NewValue=Bad" - Console.WriteLine(builder.ConnectionString) - - - Microsoft.Data.SqlClient.SqlConnectionStringBuilder builder = - new Microsoft.Data.SqlClient.SqlConnectionStringBuilder(); - builder["Data Source"] = "(local)"; - builder["Integrated Security"] = true; - builder["Initial Catalog"] = "AdventureWorks;NewValue=Bad"; - Console.WriteLine(builder.ConnectionString); - - - The result is the following connection string that handles the invalid value in a safe manner: - - - Source=(local);Initial Catalog="AdventureWorks;NewValue=Bad"; Integrated Security=True - + + class to build and modify connection strings. The class also makes it easy to manage connection strings stored in an application configuration file. + +The performs checks for valid key/value pairs. Therefore, you cannot use this class to create invalid connection strings; trying to add invalid pairs will throw an exception. The class maintains a fixed collection of synonyms and can translate from a synonym to the corresponding well-known key name. + +For example, when you use the **Item** property to retrieve a value, you can specify a string that contains any synonym for the key you need. For example, you can specify "Network Address", "addr", or any other acceptable synonym for this key within a connection string when you use any member that requires a string that contains the key name, such as the **Item** property or the method. See the property for a full list of acceptable synonyms. + +The **Item** property handles tries to insert malicious entries. For example, the following code, using the default Item property (the indexer, in C#) correctly escapes the nested key/value pair: + +```vb +Dim builder As New Microsoft.Data.SqlClient.SqlConnectionStringBuilder +builder("Data Source") = "(local)" +builder("Integrated Security") = True +builder("Initial Catalog") = "AdventureWorks;NewValue=Bad" +Console.WriteLine(builder.ConnectionString) +``` + +```csharp +Microsoft.Data.SqlClient.SqlConnectionStringBuilder builder = +new Microsoft.Data.SqlClient.SqlConnectionStringBuilder(); +builder["Data Source"] = "(local)"; +builder["Integrated Security"] = true; +builder["Initial Catalog"] = "AdventureWorks;NewValue=Bad"; +Console.WriteLine(builder.ConnectionString); + +``` + +The result is the following connection string that handles the invalid value in a safe manner: + +``` +Source=(local);Initial Catalog="AdventureWorks;NewValue=Bad"; +Integrated Security=True +``` + +]]> - - The following console application builds connection strings for a SQL Server database. The code uses a class to create the connection string, and then passes the property of the instance to the constructor of the connection class. The example also parses an existing connection string and demonstrates various ways of manipulating the connection string's contents. - - - This example includes a password to demonstrate how works with connection strings. In your applications, we recommend that you use Windows Authentication. If you must use a password, do not include a hard-coded password in your application. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - // Create a new SqlConnectionStringBuilder and - // initialize it with a few name/value pairs. - SqlConnectionStringBuilder builder = - new SqlConnectionStringBuilder(GetConnectionString()); - - // The input connection string used the - // Server key, but the new connection string uses - // the well-known Data Source key instead. - Console.WriteLine(builder.ConnectionString); - - // Pass the SqlConnectionStringBuilder an existing - // connection string, and you can retrieve and - // modify any of the elements. - builder.ConnectionString = "server=(local);integrated security=SSPI; - initial catalog=AdventureWorks"; - - // Now that the connection string has been parsed, - // you can work with individual items. - Console.WriteLine(builder.InitialCatalog); - builder.InitialCatalog = "Northwind"; - - // You can refer to connection keys using strings, - // as well. When you use this technique (the default - // Item property in Visual Basic, or the indexer in C#), - // you can specify any synonym for the connection string key - // name. - builder["Server"] = "."; - builder["Connect Timeout"] = 1000; - builder["Trusted_Connection"] = true; - Console.WriteLine(builder.ConnectionString); - - Console.WriteLine("Press Enter to finish."); - Console.ReadLine(); - } - - private static string GetConnectionString() - { - // To avoid storing the connection string in your code, - // you can retrieve it from a configuration file. - return "Server=(local);Integrated Security=SSPI;" + - "Initial Catalog=AdventureWorks"; - } - } - + + class to create the connection string, and then passes the property of the instance to the constructor of the connection class. The example also parses an existing connection string and demonstrates various ways of manipulating the connection string's contents. + +> [!NOTE] +> This example includes a password to demonstrate how works with connection strings. In your applications, we recommend that you use Windows Authentication. If you must use a password, do not include a hard-coded password in your application. + +[!code-csharp[SqlConnectionStringBuilder#1](~/../sqlclient/doc/samples/SqlConnectionStringBuilder.cs#1)] + +]]> @@ -115,52 +68,23 @@ Initializes a new instance of the class. The provided connection string provides the data for the instance's internal connection information. - The class provides a fixed internal collection of key/value pairs. Even if you supply only a small subset of the possible connection string values in the constructor, the object always provides default values for each key/value pair. When the property of the object is retrieved, the string contains only key/value pairs in which the value is not the default value for the item. + + class provides a fixed internal collection of key/value pairs. Even if you supply only a small subset of the possible connection string values in the constructor, the object always provides default values for each key/value pair. When the `ConnectionString` property of the object is retrieved, the string contains only key/value pairs in which the value is not the default value for the item. + +]]> - - The following example supplies a simple SQL Server connection string in the object's constructor, and then iterates through all the key/value pairs within the object. Note that the collection provides default values for each item. Also note that the class converts synonyms for the well-known keys so that they are consistent with the well-known names. - - - This example includes a password to demonstrate how works with connection strings. In your applications, we recommend that you use Windows Authentication. If you must use a password, do not include a hard-coded password in your application. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - try - { - string connectString = "Server=(local);Database=AdventureWorks;UID=ab;Pwd= a!Pass@@"; - Console.WriteLine("Original: " + connectString); - SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectString); - Console.WriteLine("Modified: " + builder.ConnectionString); - - foreach (string key in builder.Keys) - { - Console.WriteLine(key + "=" + builder[key].ToString()); - } - - Console.WriteLine("Press any key to finish."); - Console.ReadLine(); - - } - catch (System.Collections.Generic.KeyNotFoundException ex) - { - Console.WriteLine("KeyNotFoundException: " + ex.Message); - } - catch (System.FormatException ex) - { - Console.WriteLine("Format exception: " + ex.Message); - } - } - } - + + object's constructor, and then iterates through all the key/value pairs within the object. Note that the collection provides default values for each item. Also note that the class converts synonyms for the well-known keys so that they are consistent with the well-known names. + +> [!NOTE] +> This example includes a password to demonstrate how works with connection strings. In your applications, we recommend that you use Windows Authentication. If you must use a password, do not include a hard-coded password in your application. + +[!code-csharp[SqlConnectionStringBuilder3#1](~/../sqlclient/doc/samples/SqlConnectionStringBuilder3.cs#1)] + +]]> Invalid key name within the connection string. @@ -431,16 +355,16 @@ The number of reconnections attempted after identifying that there was an idle connection failure. This must be an integer between 0 and 255. The default value for non Azure endpoints is 1. For Azure SQL endpoints, the default is 2. Starting in version 5.x, for Azure SQL serverless or on demand endpoints, the default is 5 to improve connection success for connections to an idle or paused instance. Set to 0 to disable reconnecting on idle connection failures. An will be thrown if set to a value outside the allowed range. - - The number of reconnections attempted after identifying that there was an idle connection failure. - - - This property corresponds to the Connect Retry Count key within the connection string. - - - Since version 5.x the default value for none Azure endpoints is 1 and for Azure SQL and Azure Synapse has increased to 2 and 5 to improve the recovery against on high demand Azure endpoints. It should be detected first, and Synapse could be detected as a regular Azure SQL DB endpoint. - + + connection string. + +> [!NOTE] +> Since version 5.x the default value for none Azure endpoints is 1 and for Azure SQL and Azure Synapse has increased to 2 and 5 to improve the recovery against on high demand Azure endpoints. It should be detected first, and Synapse could be detected as a regular Azure SQL DB endpoint. + +]]> + @@ -527,7 +451,7 @@ - Because the contains a fixed-size collection of key/value pairs, the method determines only if a particular key name is valid. + Because the contains a fixed-size collection of key/value pairs, the method determines only if a particular key name is valid. @@ -615,7 +539,7 @@ - This property corresponds to the "Data Source", "server", "address", "addr", and "network address" keys within the connection string. Regardless of which of these values has been supplied within the supplied connection string, the connection string created by the SqlConnectionStringBuilder will use the well-known "Data Source" key. The port number can be specified after the server name: server=tcp:servername,portnumber . + This property corresponds to the "Data Source", "server", "address", "addr", and "network address" keys within the connection string. Regardless of which of these values has been supplied within the supplied connection string, the connection string created by the SqlConnectionStringBuilder will use the well-known "Data Source" key. The port number can be specified after the server name: server=tcp:servername,port . When specifying a local instance, always use (local). To force a protocol, add one of the following prefixes: np:(local), tcp:(local), lpc:(local) . @@ -624,8 +548,8 @@ You can also connect to a LocalDB database as follows: server=(localdb)\\myInstance . For more information about LocalDB, see SqlClient Support for LocalDB . Data Source must use the TCP format or the Named Pipes format. TCP format is as follows: - tcp:\<host name>\\<instance name> - tcp:\<host name>,\<TCP/IP port number> + tcp:\<host name>\\<instance name> + tcp:\<host name>,\<TCP/IP port number> The TCP format must start with the prefix "tcp:" and is followed by the database instance, as specified by a host name and an instance name. This format is not applicable when connecting to Azure SQL Database. TCP is automatically selected for connections to Azure SQL Database when no protocol is specified. @@ -634,9 +558,9 @@ The host name MUST be specified in one of the following ways: - NetBIOSName - IPv4Address - IPv6Address + NetBIOSName + IPv4Address + IPv6Address The instance name is used to resolve to a particular TCP/IP port number on which a database instance is hosted. Alternatively, specifying a TCP/IP port number directly is also allowed. If both instance name and port number are not present, the default database instance is used. @@ -645,7 +569,7 @@ The Named Pipes format is as follows: - - np:\\\\<host name>\pipe\\<pipe name> + np:\\\\<host name>\pipe\\<pipe name> The Named Pipes format MUST start with the prefix "np:" and is followed by a named pipe name. @@ -654,9 +578,9 @@ The host name MUST be specified in one of the following ways: - NetBIOSName - IPv4Address - IPv6Address + NetBIOSName + IPv4Address + IPv6Address The pipe name is used to identify the database instance to which the .NET application will connect. @@ -717,18 +641,19 @@ The value of the property. - - This property corresponds to the "Encrypt" key within the connection string. - - - When is and Encrypt is , or , the server name (or IP address) in a server's TLS certificate must exactly match the server name (or IP address) specified in the connection string. Otherwise, the connection attempt will fail. For information about support for certificates whose subject starts with a wildcard character (*), see Enable encrypted connections to the Database Engine . - - - Starting from version 4.0, the default value of the property Encrypt is set to while it is for earlier versions. - - - Starting from version 5.0, the data type is updated to , and the default value of the Encrypt property is set to . - + + , or `true`, the server name (or IP address) in a server's TLS certificate must exactly match the server name (or IP address) specified in the connection string. Otherwise, the connection attempt will fail. For information about support for certificates whose subject starts with a wildcard character (*), see [Enable encrypted connections to the Database Engine](https://learn.microsoft.com/sql/database-engine/configure-windows/enable-encrypted-connections-to-the-database-engine#certificate-requirements). + +> [!NOTE] +> Starting from **version 4.0**, the default value of the property `Encrypt` is set to `true` while it is `false` for earlier versions. + +> [!NOTE] +> Starting from **version 5.0**, the data type is updated to , and the default value of the `Encrypt` property is set to . + + ]]> Working with Connection Strings @@ -739,7 +664,7 @@ - Gets or sets a Boolean value that indicates whether the SQL Server connection pooler automatically enlists the connection in the creation thread's current transaction context. + Gets or sets a Boolean value that indicates whether the SQL Server connection pool automatically enlists the connection in the creation thread's current transaction context. The value of the property, or if none has been supplied. @@ -781,12 +706,15 @@ The value of the property, or if none has been supplied. - - This property corresponds to the "FailoverPartnerSPN" and "Failover Partner SPN" keys within the connection string. - - - This property only applies when using Integrated Security mode, otherwise it is ignored. - + + [!NOTE] +> This property only applies when using Integrated Security mode, otherwise it is ignored. + +]]> + @@ -797,12 +725,15 @@ The value of the property, or if none has been supplied. - - This property corresponds to the "HostNameInCertificate" and "Host Name in Certificate" keys within the connection string. - - - This property only applies when using in or mode, otherwise it is ignored. - + + [!NOTE] +> This property only applies when using `Encrypt` in or mode, otherwise it is ignored. + +]]> + @@ -882,71 +813,27 @@ The value of the property, or if none has been supplied. - - This property corresponds to the "Integrated Security" and "trusted_connection" keys within the connection string. - - - If User ID and Password are specified and Integrated Security is set to true, the User ID and Password will be ignored and Integrated Security will be used. is a more secure way to specify credentials for a connection that uses SQL Server Authentication ( Integrated Security=false ). - + + is a more secure way to specify credentials for a connection that uses SQL Server Authentication (`Integrated Security=false`). + +]]> - - The following example converts an existing connection string from using SQL Server Authentication to using integrated security. The example does its work by removing the username and password from the connection string and then setting the property of the object. - - - This example includes a password to demonstrate how works with connection strings. In your applications, we recommend that you use Windows Authentication. If you must use a password, do not include a hard-coded password in your application. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - try - { - string connectString = - "Data Source=(local);Integrated Security=SSPI" + - "Initial Catalog=AdventureWorks"; - - SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectString); - Console.WriteLine("Original: " + builder.ConnectionString); - - // Use the Remove method - // in order to reset the user ID and password back to their - // default (empty string) values. Simply setting the - // associated property values to an empty string won't - // remove them from the connection string; you must - // call the Remove method. - builder.Remove("User ID"); - builder.Remove("Password"); - - // Turn on integrated security: - builder.IntegratedSecurity = true; - - Console.WriteLine("Modified: " + builder.ConnectionString); - - using (SqlConnection connection = new SqlConnection(builder.ConnectionString)) - { - connection.Open(); - // Now use the open connection. - Console.WriteLine("Database = " + connection.Database); - } - - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } - - Console.WriteLine("Press any key to finish."); - Console.ReadLine(); - } - } - + + property of the object. + +> [!NOTE] +> This example includes a password to demonstrate how works with connection strings. In your applications, we recommend that you use Windows Authentication. If you must use a password, do not include a hard-coded password in your application. + +[!code-csharp[SqlConnectionStringBuilder_IntegratedSecurity#1](~/../sqlclient/doc/samples/SqlConnectionStringBuilder_IntegratedSecurity.cs#1)] + +]]> @@ -989,7 +876,7 @@ - The following code, in a console application, creates a new and adds key/value pairs to its connection string, using the property. + The following code, in a console application, creates a new and adds key/value pairs to its connection string, using the property. @@ -1183,13 +1070,13 @@ This property corresponds to the "Network Library", "network", and "net" keys within the connection string. Supported values for this property include: - dbnmpntw (Named Pipes) - dbmsrpcn (Multiprotocol) - dbmsadsn (AppleTalk) - dbmsgnet (VIA) - dbmslpcn (Shared Memory) - dbmsspxn (IPX/SPX) - dbmssocn (TCP/IP) + dbnmpntw (Named Pipes) + dbmsrpcn (Multiprotocol) + dbmsadsn (AppleTalk) + dbmsgnet (VIA) + dbmslpcn (Shared Memory) + dbmsspxn (IPX/SPX) + dbmssocn (TCP/IP) The corresponding network DLL must be installed on the system to which you connect. If you do not specify a network and you use a local server (for example, "." or "(local)"), Shared Memory is used. @@ -1337,182 +1224,61 @@ if the key existed within the connection string and was removed; if the key did not exist. - - Because the Remove method returns a value that indicates its success, it is not required to look for a key before trying to remove the key/value pair from the instance. Because the maintains a fixed-size collection of key/value pairs, calling the Remove method simply resets the value of the key/value pair back to its default value. - - - Because the collection of keys supported by the is fixed, every item within the collection has a known default value. The following table lists the keys, and the value for each when the is first initialized, or after the Remove method has been called. - - - - Key - Default value - - - Application Name - - "Framework Microsoft SqlClient Data Provider" when running on .NET Framework. "Core Microsoft SqlClient Data Provider" otherwise. - - - - AttachDBFilename - Empty string - - - Connection Timeout - 15 - - - Context Connection(Obsolete) - False - - - Current Language - Empty string - - - Data Source - Empty string - - - Encrypt - False in versions prior to 4.0, True in versions 4.0 and up - - - Enlist - True - - - Failover Partner - Empty string - - - Initial Catalog - Empty string - - - Integrated Security - False - - - Load Balance Timeout - 0 - - - Max Pool Size - 100 - - - Min Pool Size - 0 - - - MultipleActiveResultSets - False - - - Network Library - Empty string - - - Packet Size - 8000 - - - Password - Empty string - - - Persist Security Info - False - - - Pooling - True - - - Replication - False - - - Transaction Binding - Implicit Unbind - - - User ID - Empty string - - - User Instance - False - - - Workstation ID - Empty string - - - - - - The following example converts an existing connection string from using Windows Authentication to using integrated security. The example works by removing the username and password from the connection string, and then setting the property of the object. - - - This example includes a password to demonstrate how works with connection strings. In your applications, we recommend that you use Windows Authentication. If you must use a password, do not include a hard-coded password in your application. - - - - using Microsoft.Data.SqlClient; + + instance. Because the maintains a fixed-size collection of key/value pairs, calling the method simply resets the value of the key/value pair back to its default value. - class Program - { - static void Main() - { - try - { - string connectString = - "Data Source=(local);User ID=ab;"; +Because the collection of keys supported by the is fixed, every item within the collection has a known default value. The following table lists the keys, and the value for each when the is first initialized, or after the method has been called. - SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connectString); - Console.WriteLine("Original: " + builder.ConnectionString); +|Key|Default value| +|---------|-------------------| +|Application Name|"Framework Microsoft SqlClient Data Provider" when running on .NET Framework. "Core Microsoft SqlClient Data Provider" otherwise.| +|AttachDBFilename|Empty string| +|Connection Timeout|15| +|Context Connection(Obsolete)|False| +|Current Language|Empty string| +|Data Source|Empty string| +|Encrypt|False in versions prior to 4.0, True in versions 4.0 and up| +|Enlist|True| +|Failover Partner|Empty string| +|Initial Catalog|Empty string| +|Integrated Security|False| +|Load Balance Timeout|0| +|Max Pool Size|100| +|Min Pool Size|0| +|MultipleActiveResultSets|False| +|Network Library|Empty string| +|Packet Size|8000| +|Password|Empty string| +|Persist Security Info|False| +|Pooling|True| +|Replication|False| +|Transaction Binding|Implicit Unbind| +|User ID|Empty string| +|User Instance|False| +|Workstation ID|Empty string| - // Use the Remove method - // in order to reset the user ID back to its - // default (empty string) value. - builder.Remove("User ID"); +]]> + + + + property of the object. - // Turn on integrated security: - builder.IntegratedSecurity = true; +> [!NOTE] +> This example includes a password to demonstrate how works with connection strings. In your applications, we recommend that you use Windows Authentication. If you must use a password, do not include a hard-coded password in your application. - // Turn off encryption for local db - builder.Encrypt = false; +[!code-csharp[SqlConnectionStringBuilder_Remove#1](~/../sqlclient/doc/samples/SqlConnectionStringBuilder_Remove.cs#1)] - Console.WriteLine("Modified: " + builder.ConnectionString); +The example displays the following text in the console window: - using (SqlConnection connection = new SqlConnection(builder.ConnectionString)) - { - connection.Open(); - // Now use the open connection. - Console.WriteLine("Database = " + connection.Database); - } - } - catch (Exception ex) - { - Console.WriteLine(ex.Message); - } +``` +Original: Data Source=(local);Initial Catalog=AdventureWorks;User ID=ab;Password=******** +Modified: Data Source=(local);Initial Catalog=AdventureWorks;Integrated Security=True +Database = AdventureWorks +``` - Console.WriteLine("Press any key to finish."); - Console.ReadLine(); - } - } - - - The example displays the following text in the console window: - - - Original: Data Source=(local);User ID=ab; - Modified: Data Source=(local);Integrated Security=True; Encrypt=false - Database = master - +]]> is null ( in Visual Basic) @@ -1537,12 +1303,15 @@ The value of the property, or string.Empty if none has been supplied. - - This property corresponds to the "ServerCertificate" and "Server Certificate" keys within the connection string. - - - This property only applies when using Encrypt in or mode, otherwise it is ignored. - + + [!NOTE] +> This property only applies when using Integrated Security mode, otherwise it is ignored. + +]]> + @@ -1553,12 +1322,15 @@ The value of the property, or if none has been supplied. - - This property corresponds to the "ServerSPN" and "Server SPN" keys within the connection string. - - - This property only applies when using Integrated Security mode, otherwise it is ignored. - + + [!NOTE] +> This property only applies when using Integrated Security mode, otherwise it is ignored. + +]]> + @@ -1632,18 +1404,21 @@ - Connecting to Azure SQL Database where the data source ends with: - - .database.chinacloudapi.cn - .database.usgovcloudapi.net - .database.cloudapi.de - .database.windows.net - + + Connecting to Azure SQL Database where the data source ends with: + + .database.chinacloudapi.cn + .database.usgovcloudapi.net + .database.cloudapi.de + .database.windows.net + .database.fabric.microsoft.com + + - + Authentication is 'Active Directory Password' or 'Active Directory Integrated' - - Otherwise it defaults to true. + + Otherwise it defaults to true. @@ -1681,7 +1456,7 @@ - The method lets developers safely retrieve a value from a without needing to verify that the supplied key name is a valid key name. Because TryGetValue does not raise an exception when you call it, passing in a nonexistent key, you do not have to look for a key before retrieving its value. Calling TryGetValue with a nonexistent key will place the value null (Nothing in Visual Basic) in the parameter. + The method lets developers safely retrieve a value from a without needing to verify that the supplied key name is a valid key name. Because TryGetValue does not raise an exception when you call it, passing in a nonexistent key, you do not have to look for a key before retrieving its value. Calling TryGetValue with a nonexistent key will place the value null (Nothing in Visual Basic) in the parameter. @@ -1823,12 +1598,14 @@ The value of the property, or if none has been supplied. - - This property corresponds to the "User Instance" key within the connection string. - - - This feature is available only with the SQL Server Express Edition. For more information on user instances, see SQL Server Express User Instances. - + + [!NOTE] +> This feature is available only with the SQL Server Express Edition. For more information on user instances, see [SQL Server Express User Instances](https://learn.microsoft.com/sql/connect/ado-net/sql/sql-server-express-user-instances). + +]]> To set the value to null, use . diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlCredential.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlCredential.xml index d8005456b6..c080526ec8 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlCredential.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlCredential.xml @@ -6,15 +6,15 @@ - Use to get or set a connection's object. Use to change the password for a object. For information on how a object affects connection pool behavior, see SQL Server Connection Pooling (ADO.NET). + Use to get or set a connection's object. Use to change the password for a object. For information on how a object affects connection pool behavior, see SQL Server Connection Pooling (ADO.NET). An exception will be raised if a non-null object is used in a connection with any of the following connection string keywords: - Integrated Security = true - Password - User ID + Integrated Security = true + Password + User ID diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlDataAdapter.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlDataAdapter.xml index 8013767227..7a2f32a0bf 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlDataAdapter.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlDataAdapter.xml @@ -5,61 +5,33 @@ Represents a set of data commands and a database connection that are used to fill the and update a SQL Server database. This class cannot be inherited. - - The , serves as a bridge between a and SQL Server for retrieving and saving data. The provides this bridge by mapping , which changes the data in the to match the data in the data source, and , which changes the data in the data source to match the data in the , using the appropriate Transact-SQL statements against the data source. The update is performed on a by-row basis. For every inserted, modified, and deleted row, the method determines the type of change that has been performed on it (Insert, Update, or Delete). Depending on the type of change, the Insert, Update, or Delete command template executes to propagate the modified row to the data source. When the fills a , it creates the necessary tables and columns for the returned data if they do not already exist. However, primary key information is not included in the implicitly created schema unless the property is set to . You may also have the create the schema of the , including primary key information, before filling it with data using FillSchema. For more information, see Adding Existing Constraints to a DataSet. is used in conjunction with and to increase performance when connecting to a SQL Server database. - - - If you are using SQL Server stored procedures to edit or delete data using a DataAdapter, make sure that you do not use SET NOCOUNT ON in the stored procedure definition. This causes the rows affected count returned to be zero, which the DataAdapter interprets as a concurrency conflict. In this event, a will be thrown. - - - The also includes the , , , , and properties to facilitate the loading and updating of data. - - - When an instance of is created, the read/write properties are set to initial values. For a list of these values, see the constructor. - - - The , , and are generic templates that are automatically filled with individual values from every modified row through the parameters mechanism. - - - For every column that you propagate to the data source on , a parameter should be added to the InsertCommand, UpdateCommand, or DeleteCommand. The property of the object should be set to the name of the column. This setting indicates that the value of the parameter is not set manually, but is taken from the particular column in the currently processed row. - - - An will occur if the method is called and the table contains a user-defined type that is not available on the client computer. For more information, see CLR User-Defined Types. - + , serves as a bridge between a and SQL Server for retrieving and saving data. The provides this bridge by mapping , which changes the data in the to match the data in the data source, and , which changes the data in the data source to match the data in the , using the appropriate Transact-SQL statements against the data source. The update is performed on a by-row basis. For every inserted, modified, and deleted row, the method determines the type of change that has been performed on it (`Insert`, `Update`, or `Delete`). Depending on the type of change, the `Insert`, `Update`, or `Delete` command template executes to propagate the modified row to the data source. When the fills a , it creates the necessary tables and columns for the returned data if they do not already exist. However, primary key information is not included in the implicitly created schema unless the property is set to . You may also have the create the schema of the , including primary key information, before filling it with data using `FillSchema`. For more information, see [Adding Existing Constraints to a DataSet](https://learn.microsoft.com/sql/connect/ado-net/add-existing-constraints-to-dataset). + + is used in conjunction with and to increase performance when connecting to a SQL Server database. + +> [!NOTE] +> If you are using SQL Server stored procedures to edit or delete data using a `DataAdapter`, make sure that you do not use SET NOCOUNT ON in the stored procedure definition. This causes the rows affected count returned to be zero, which the `DataAdapter` interprets as a concurrency conflict. In this event, a will be thrown. + +The also includes the , , , , and properties to facilitate the loading and updating of data. + +When an instance of is created, the read/write properties are set to initial values. For a list of these values, see the constructor. + +The , , and are generic templates that are automatically filled with individual values from every modified row through the parameters mechanism. + +For every column that you propagate to the data source on , a parameter should be added to the `InsertCommand`, `UpdateCommand`, or `DeleteCommand`. The property of the object should be set to the name of the column. This setting indicates that the value of the parameter is not set manually, but is taken from the particular column in the currently processed row. + +> [!NOTE] +> An will occur if the method is called and the table contains a user-defined type that is not available on the client computer. For more information, see [CLR User-Defined Types](https://learn.microsoft.com/sql/relational-databases/clr-integration-database-objects-user-defined-types/clr-user-defined-types). + +## Examples +The following example uses the , , and to select records from a database and populate a with the selected rows. The filled is then returned. To accomplish this, the method is passed an initialized , a connection string, and a query string that is a Transact-SQL SELECT statement. + +[!code-csharp[SqlDataAdapter_SelectCommand Example#1](~/../sqlclient/doc/samples/SqlDataAdapter_SelectCommand.cs#1)] + +]]> - - - The following example uses the , , and to select records from a database and populate a with the selected rows. The filled is then returned. To accomplish this, the method is passed an initialized , a connection string, and a query string that is a Transact-SQL SELECT statement. - - - - using System; - using System.Data; - using System.Data.Common; - using System.Windows.Forms; - using System.Xml; - using Microsoft.Data.SqlClient; - - public class Form1 : Form - { - protected DataSet DataSet1; - protected DataGrid dataGrid1; - - private static DataSet SelectRows(DataSet dataset, - string connectionString, string queryString) - { - using (SqlConnection connection = new SqlConnection(connectionString)) - { - SqlDataAdapter adapter = new SqlDataAdapter(); - adapter.SelectCommand = new SqlCommand( - queryString, connection); - adapter.Fill(dataset); - return dataset; - } - } - } - - @@ -860,13 +832,13 @@ When using , there are two events that occur per data row updated. The order of execution is as follows: - The values in the are moved to the parameter values. - The event is raised. - The command executes. - If the command is set to FirstReturnedRecord, the first returned result is placed in the . - If there are output parameters, they are placed in the . - The event is raised. - is called. + The values in the are moved to the parameter values. + The event is raised. + The command executes. + If the command is set to FirstReturnedRecord, the first returned result is placed in the . + If there are output parameters, they are placed in the . + The event is raised. + is called. @@ -973,13 +945,13 @@ When using , there are two events that occur per data row updated. The order of execution is as follows: - The values in the are moved to the parameter values. - The event is raised. - The command executes. - If the command is set to FirstReturnedRecord, the first returned result is placed in the . - If there are output parameters, they are placed in the . - The event is raised. - is called. + The values in the are moved to the parameter values. + The event is raised. + The command executes. + If the command is set to FirstReturnedRecord, the first returned result is placed in the . + If there are output parameters, they are placed in the . + The event is raised. + is called. @@ -1282,93 +1254,26 @@ A used during to update records in the database that correspond to modified rows in the . - - During , if this property is not set and primary key information is present in the , the can be generated automatically if you set the property and use the . Then, any additional commands that you do not set are generated by the . This generation logic requires key column information to be present in the . For more information, see Generating Commands with CommandBuilders. - - - When is assigned to a previously created , the is not cloned. The maintains a reference to the previously created object. - - - If execution of this command returns rows, the updated rows may be merged with the depending on how you set the property of the object. - - - For every column that you propagate to the data source on , a parameter should be added to , UpdateCommand, or . - - - The SourceColumn property of the parameter should be set to the name of the column. This indicates that the value of the parameter is not set manually, but taken from the particular column in the currently processed row. - - - - - The following example creates a and sets the , , and properties. It assumes you have already created a object. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - } - - public static SqlDataAdapter CreateCustomerAdapter(SqlConnection connection) - { - SqlDataAdapter adapter = new SqlDataAdapter(); - - // Create the SelectCommand. - SqlCommand command = new SqlCommand("SELECT * FROM Customers " + - "WHERE Country = @Country AND City = @City", connection); - - // Add the parameters for the SelectCommand. - command.Parameters.Add("@Country", SqlDbType.NVarChar, 15); - command.Parameters.Add("@City", SqlDbType.NVarChar, 15); - - adapter.SelectCommand = command; - - // Create the InsertCommand. - command = new SqlCommand( - "INSERT INTO Customers (CustomerID, CompanyName) " + - "VALUES (@CustomerID, @CompanyName)", connection); - - // Add the parameters for the InsertCommand. - command.Parameters.Add("@CustomerID", SqlDbType.NChar, 5, "CustomerID"); - command.Parameters.Add("@CompanyName", SqlDbType.NVarChar, 40, "CompanyName"); - - adapter.InsertCommand = command; - - // Create the UpdateCommand. - command = new SqlCommand( - "UPDATE Customers SET CustomerID = @CustomerID, CompanyName = @CompanyName " + - "WHERE CustomerID = @oldCustomerID", connection); - - // Add the parameters for the UpdateCommand. - command.Parameters.Add("@CustomerID", SqlDbType.NChar, 5, "CustomerID"); - command.Parameters.Add("@CompanyName", SqlDbType.NVarChar, 40, "CompanyName"); - SqlParameter parameter = command.Parameters.Add( - "@oldCustomerID", SqlDbType.NChar, 5, "CustomerID"); - parameter.SourceVersion = DataRowVersion.Original; - - adapter.UpdateCommand = command; - - // Create the DeleteCommand. - command = new SqlCommand( - "DELETE FROM Customers WHERE CustomerID = @CustomerID", connection); - - // Add the parameters for the DeleteCommand. - parameter = command.Parameters.Add( - "@CustomerID", SqlDbType.NChar, 5, "CustomerID"); - parameter.SourceVersion = DataRowVersion.Original; - - adapter.DeleteCommand = command; - - return adapter; - } - } - - + , if this property is not set and primary key information is present in the , the can be generated automatically if you set the property and use the . Then, any additional commands that you do not set are generated by the . This generation logic requires key column information to be present in the . For more information, see [Generating Commands with CommandBuilders](https://learn.microsoft.com/sql/connect/ado-net/generate-commands-with-commandbuilders). + +When is assigned to a previously created , the is not cloned. The maintains a reference to the previously created object. + +> [!NOTE] +> If execution of this command returns rows, the updated rows may be merged with the depending on how you set the **UpdatedRowSource** property of the object. + +For every column that you propagate to the data source on , a parameter should be added to `InsertCommand`, `UpdateCommand`, or `DeleteCommand`. + +The `SourceColumn` property of the parameter should be set to the name of the column. This indicates that the value of the parameter is not set manually, but taken from the particular column in the currently processed row. + +## Examples +The following example creates a and sets the , , and properties. It assumes you have already created a object. + +[!code-csharp[SqlDataAdapter#1](~/../sqlclient/doc/samples/SqlDataAdapter.cs#1)] + +]]> + diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml index ad997ad909..1c287a0de4 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlDataReader.xml @@ -5,136 +5,50 @@ Provides a way of reading a forward-only stream of rows from a SQL Server database. This class cannot be inherited. - - To create a , you must call the method of the object, instead of directly using a constructor. - - - While the is being used, the associated is busy serving the , and no other operations can be performed on the other than closing it. This is the case until the method of the is called. For example, you cannot retrieve output parameters until after you call . - - - Changes made to a result set by another process or thread while data is being read may be visible to the user of the SqlDataReader. However, the precise behavior is timing dependent. and are the only properties that you can call after the is closed. Although the property may be accessed while the exists, always call before returning the value of to guarantee an accurate return value. - - - When using sequential access (), an will be raised if the position is advanced and another read operation is attempted on the previous column. - - - For optimal performance, avoids creating unnecessary objects or making unnecessary copies of data. Therefore, multiple calls to methods such as return a reference to the same object. Use caution if you are modifying the underlying value of the objects returned by methods such as . - - - - - The following example creates a , a , and a . The example reads through the data, writing it out to the console window. The code then closes the . The is closed automatically at the end of the using code block. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string str = "Data Source=(local);Initial Catalog=Northwind;" - + "Integrated Security=SSPI"; - ReadOrderData(str); - } - - private static void ReadOrderData(string connectionString) - { - string queryString = "SELECT OrderID, CustomerID FROM dbo.Orders;"; - - using (SqlConnection connection = - new SqlConnection(connectionString)) - { - SqlCommand command = - new SqlCommand(queryString, connection); - connection.Open(); - - SqlDataReader reader = command.ExecuteReader(); - - // Call Read before accessing data. - while (reader.Read()) - { - ReadSingleRow((IDataRecord)reader); - } - - // Call Close when done reading. - reader.Close(); - } - } - - private static void ReadSingleRow(IDataRecord record) - { - Console.WriteLine(String.Format("{0}, {1}", record[0], record[1])); - } - } - - + , you must call the method of the object, instead of directly using a constructor. + +While the is being used, the associated is busy serving the , and no other operations can be performed on the other than closing it. This is the case until the method of the is called. For example, you cannot retrieve output parameters until after you call . + +Changes made to a result set by another process or thread while data is being read may be visible to the user of the `SqlDataReader`. However, the precise behavior is timing dependent. + + and are the only properties that you can call after the is closed. Although the property may be accessed while the exists, always call before returning the value of to guarantee an accurate return value. + +When using sequential access (), an will be raised if the position is advanced and another read operation is attempted on the previous column. + +> [!NOTE] +> For optimal performance, avoids creating unnecessary objects or making unnecessary copies of data. Therefore, multiple calls to methods such as return a reference to the same object. Use caution if you are modifying the underlying value of the objects returned by methods such as . + +## Examples +The following example creates a , a , and a . The example reads through the data, writing it out to the console window. The code then closes the . The is closed automatically at the end of the `using` code block. + +[!code-csharp[SqlDataReader_Read Example#1](~/../sqlclient/doc/samples/SqlDataReader_Read.cs#1)] + +]]> + Closes the object. - - You must ensure the method is called when you are through using the before using the associated for any other purpose. The Close method may either be called directly or through the method, disposing directly or in the context of - the using statement - block. - - - The Close method populates the values for output parameters, return values and on the by consuming any pending results. This may be a long operation depending on the amount of data to be consumed. If output values, return values, and are not important to your application, the time to close may be shortened by calling the method of the associated object before the Close method is called. - - - Do not call Close or on a Connection, a DataReader, or any other managed object in the finalizer method of your class. In a finalizer, you should only release unmanaged resources that your class owns directly. If your class does not own any unmanaged resources, do not include a finalizer method in your class definition. For more information, see Garbage Collection. - - - - - The following example creates a , a , and a . The example reads through the data, writing it out to the console window. The code then closes the . The is closed automatically at the end of the using code block. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - static void Main() - { - string str = "Data Source=(local);Initial Catalog=Northwind;" - + "Integrated Security=SSPI"; - ReadOrderData(str); - } - - private static void ReadOrderData(string connectionString) - { - string queryString = "SELECT OrderID, CustomerID FROM dbo.Orders;"; - - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - using (SqlCommand command = new SqlCommand(queryString, connection)) - { - using (SqlDataReader reader = command.ExecuteReader()) - { - // Call Read before accessing data. - while (reader.Read()) - { - Console.WriteLine(String.Format("{0}, {1}", reader[0], reader[1])); - } - - // Call Close when done reading. - reader.Close(); - } - } - } - } - } - - + method is called when you are through using the before using the associated for any other purpose. The `Close` method may either be called directly or through the `Dispose` method, disposing directly or in the context of [the using statement](https://learn.microsoft.com/dotnet/csharp/language-reference/language-specification/statements#the-using-statement) block. + +The `Close` method populates the values for output parameters, return values and `RecordsAffected` on the by consuming any pending results. This may be a long operation depending on the amount of data to be consumed. If output values, return values, and `RecordsAffected` are not important to your application, the time to close may be shortened by calling the method of the associated object before the `Close` method is called. + +> [!CAUTION] +> Do not call `Close` or `Dispose` on a Connection, a DataReader, or any other managed object in the `Finalize` method of your class. In a finalizer, you should only release unmanaged resources that your class owns directly. If your class does not own any unmanaged resources, do not include a `Finalize` method in your class definition. For more information, see [Garbage Collection](https://learn.microsoft.com/dotnet/standard/garbage-collection/). + +## Examples +The following example creates a , a , and a . The example reads through the data, writing it out to the console window. The code then closes the . The is closed automatically at the end of the `using` code block. + +[!code-csharp[SqlDataReader_Close Example#1](~/../sqlclient/doc/samples/SqlDataReader_Close.cs#1)] + +]]> + @@ -298,18 +212,18 @@ The actual number of characters read. - - GetChars returns the number of available characters in the field. Frequently this is the exact length of the field. However, the number returned may be less than the true length of the field if GetChars has already been used to obtain characters from the field. This may be the case, for example, if the is reading a large data structure into a buffer. For more information, see the setting for . - - - The actual number of characters read can be less than the requested length, if the end of the field is reached. If you pass a buffer that is , GetChars returns the length of the entire field in characters, not the remaining size based on the buffer offset parameter. - - - No conversions are performed; therefore. the data retrieved must already be a character array. - - - The GetChars method returns 0 when is negative. - + returns the number of available characters in the field. Frequently this is the exact length of the field. However, the number returned may be less than the true length of the field if `GetChars` has already been used to obtain characters from the field. This may be the case, for example, if the is reading a large data structure into a buffer. For more information, see the `SequentialAccess` setting for . + +The actual number of characters read can be less than the requested length, if the end of the field is reached. If you pass a buffer that is `null`, returns the length of the entire field in characters, not the remaining size based on the buffer offset parameter. + +No conversions are performed; therefore. the data retrieved must already be a character array. + +> [!NOTE] +> The method returns 0 when `dataIndex` is negative. + +]]> @@ -317,7 +231,7 @@ Gets the read-only column schema collection. - The read-only column schema collection). + The read-only column schema collection. This method is an implementation of method, which enables the use of the interface to populate the schema metadata without using a . @@ -476,37 +390,38 @@ can be one of the following types: - Boolean (bool) - Byte - Char - DateOnly (.NET 6 or later) - DateTime - DateTimeOffset - Decimal - Double - Single (float) - Guid - Int16 (short) - Int32 (int) - Int64 (long) - SqlBoolean - SqlByte - SqlDateTime - SqlDecimal - SqlDouble - SqlGuid - SqlInt16 - SqlInt32 - SqlInt64 - SqlMoney - SqlSingle - SqlString - Stream - String - TextReader - TimeOnly (.NET 6 or later) - XmlReader - UDT, which can be any CLR type marked with . + Boolean (bool) + Byte + Char + DateOnly (.NET 6 or later) + DateTime + DateTimeOffset + Decimal + Double + Single (float) + Guid + Int16 (short) + Int32 (int) + Int64 (long) + SqlBoolean + SqlByte + SqlDateTime + SqlDecimal + SqlDouble + SqlGuid + SqlInt16 + SqlInt32 + SqlInt64 + SqlMoney + SqlSingle + SqlString + SqlVector + Stream + String + TextReader + TimeOnly (.NET 6 or later) + XmlReader + UDT, which can be any CLR type marked with . For more information, see SqlClient Streaming Support. @@ -514,11 +429,11 @@ - The connection drops or is closed during the data retrieval. - The is closed during the data retrieval. - There is no data ready to be read (for example, the first hasn't been called, or returned false). - Tried to read a previously-read column in sequential mode. - There was an asynchronous operation in progress. This applies to all Get* methods when running in sequential mode, as they could be called while reading a stream. + The connection drops or is closed during the data retrieval. + The is closed during the data retrieval. + There is no data ready to be read (for example, the first hasn't been called, or returned false). + Tried to read a previously-read column in sequential mode. + There was an asynchronous operation in progress. This applies to all Get* methods when running in sequential mode, as they could be called while reading a stream. @@ -550,37 +465,38 @@ can be one of the following types: - Boolean (bool) - Byte - Char - DateOnly (.NET 6 or later) - DateTime - DateTimeOffset - Decimal - Double - Single (float) - Guid - Int16 (short) - Int32 (int) - Int64 (long) - SqlBoolean - SqlByte - SqlDateTime - SqlDecimal - SqlDouble - SqlGuid - SqlInt16 - SqlInt32 - SqlInt64 - SqlMoney - SqlSingle - SqlString - Stream - String - TextReader - TimeOnly (.NET 6 or later) - XmlReader - UDT, which can be any CLR type marked with . + Boolean (bool) + Byte + Char + DateOnly (.NET 6 or later) + DateTime + DateTimeOffset + Decimal + Double + Single (float) + Guid + Int16 (short) + Int32 (int) + Int64 (long) + SqlBoolean + SqlByte + SqlDateTime + SqlDecimal + SqlDouble + SqlGuid + SqlInt16 + SqlInt32 + SqlInt64 + SqlMoney + SqlSingle + SqlString + SqlVector + Stream + String + TextReader + TimeOnly (.NET 6 or later) + XmlReader + UDT, which can be any CLR type marked with . For more information, see SqlClient Streaming Support. @@ -588,11 +504,11 @@ - The connection drops or is closed during the data retrieval. - The is closed during the data retrieval. - There is no data ready to be read (for example, the first hasn't been called, or returned false). - Tried to read a previously-read column in sequential mode. - There was an asynchronous operation in progress. This applies to all Get* methods when running in sequential mode, as they could be called while reading a stream. + The connection drops or is closed during the data retrieval. + The is closed during the data retrieval. + There is no data ready to be read (for example, the first hasn't been called, or returned false). + Tried to read a previously-read column in sequential mode. + There was an asynchronous operation in progress. This applies to all Get* methods when running in sequential mode, as they could be called while reading a stream. @@ -832,254 +748,48 @@ A that describes the column metadata. - - The method returns the following metadata about each column: - - - - DataReader column - Description - - - AllowDBNull - - Set if the consumer can set the column to a null value or if the provider cannot determine whether the consumer can set the column to a null value. Otherwise, not set. A column may contain null values, even if it cannot be set to a null value. - - - - BaseCatalogName - - The name of the catalog in the data store that contains the column. NULL if the base catalog name cannot be determined. The default of this column is a null value. - - - - BaseColumnName - - The name of the column in the data store. This might be different than the column name returned in the ColumnName column if an alias was used. A null value if the base column name cannot be determined or if the rowset column is derived, but not identical to, a column in the data store. The default of this column is a null value. - - - - BaseSchemaName - - The name of the schema in the data store that contains the column. A null value if the base schema name cannot be determined. The default of this column is a null value. - - - - BaseServerName - - The name of the instance of Microsoft SQL Server used by the . - - - - BaseTableName - - The name of the table or view in the data store that contains the column. A null value if the base table name cannot be determined. The default of this column is a null value. - - - - ColumnName - - The name of the column; this might not be unique. If this cannot be determined, a null value is returned. This name always reflects the most recent renaming of the column in the current view or command text. - - - - ColumnOrdinal - - The zero-based ordinal of the column. This column cannot contain a null value. - - - - ColumnSize - - - The maximum possible length of a value in the column. For columns that use a fixed-length data type, this is the size of the data type. For nvarchar(MAX), varchar(MAX), and varbinary(MAX) columns stored in a SQL Server database, the maximum size is 2GB. If these columns are stored and accessed as files, the limit on maximum size is imposed by the file system. This value changes when using the Type System Version keyword in the connection string. - - - For new types they are represented as downlevel types. The MAX data types return the normal 4k for nvarchar and 8000 for varchar. For more information, see the Transact-SQL reference. - - - - - DataTypeName - - Returns a string representing the data type of the specified column. - - - - IsAliased - - - : The column name is an alias. - - - : The column name is not an alias. - - - - - IsAutoIncrement - - - : The column assigns values to new rows in fixed increments. - - - : The column does not assign values to new rows in fixed increments. The default of this column is - - - - - IsColumnSet - - : The column is a sparse column that is a member of a column set. - - - - IsExpression - - - : The column is an expression. - - - : The column is not an expression. - - - - - IsHidden - - - : The column is hidden. - - - : The column is not hidden. - - - - - IsIdentity - - - : The column is an identity column. - - - : The column is not an identity column. - - - - - IsKey - - - : The column is one of a set of columns in the rowset that, taken together, uniquely identify the row. The set of columns with IsKey set to must uniquely identify a row in the rowset. There is no requirement that this set of columns is a minimal set of columns. This set of columns may be generated from a base table primary key, a unique constraint or a unique index. - - - : The column is not required to uniquely identify the row. - - - - - IsLong - - - : The column contains a Binary Long Object (BLOB) that contains very long data. The definition of very long data is provider-specific. - - - : The column does not contain a Binary Long Object (BLOB) that contains very long data. - - - - - IsReadOnly - - - : The column cannot be modified. - - - : The column can be modified. - - - - - IsRowVersion - - - : The column contains a persistent row identifier that cannot be written to, and has no meaningful value except to identity the row. - - - : The column does not contain a persistent row identifier that cannot be written to, and has no meaningful value except to identity the row. - - - - - IsUnique - - - : Column is of type timestamp. - - - : Column is not of type timestamp. - - - - - NonVersionedProviderType - - The type of the column irrespective of the current Type System Version specified in the connection string. The returned value is from the enumeration. - - - - NumericPrecision - - If ProviderType is a numeric data type, this is the maximum precision of the column. The precision depends on the definition of the column. If ProviderType is not a numeric data type, this is 255. - - - - NumericScale - - If ProviderType is DBTYPE_DECIMAL or DBTYPE_NUMERIC, the number of digits to the right of the decimal point. Otherwise, this is 255. - - - - ProviderSpecificDataType - - Returns the provider-specific data type of the column based on the Type System Version keyword in the connection string. - - - - ProviderType - - The indicator of the column's data type. If the data type of the column varies from row to row, this must be Object. This column cannot contain a null value. - - - - UdtAssemblyQualifiedName - - If the column is a user-defined type (UDT), this is the qualified name of the UDT's assembly as per . If the column is not a UDT, this is null. - - - - XmlSchemaCollectionDatabase - - The name of the database where the schema collection for this XML instance is located, if the row contains information about an XML column. This value is null (Nothing in Visual Basic) if the collection is defined within the current database. It is also null if there is no schema collection, in which case the XmlSchemaCollectionName and XmlSchemaCollectionOwningSchema columns are also null. - - - - XmlSchemaCollectionName - - The name of the schema collection for this XML instance, if the row contains information about an XML column. This value is null (Nothing in Visual Basic) if there is no associated schema collection. If the value is null, the XmlSchemaCollectionDatabase and XmlSchemaCollectionOwningSchema columns are also null. - - - - XmlSchemaCollectionOwningSchema - - The owning relational schema where the schema collection for this XML instance is located, if the row contains information about an XML column. This value is null (Nothing in Visual Basic) if the collection is defined within the current database. It is also null if there is no schema collection, in which case the XmlSchemaCollectionDatabase and XmlSchemaCollectionName columns are also null. - - - - - To make sure that metadata columns return the correct information, you must call with the behavior parameter set to KeyInfo. Otherwise, some of the columns in the schema table may return default, null, or incorrect data. - - + method returns the following metadata about each column: + +|DataReader column|Description| +|-----------------------|-----------------| +|AllowDBNull|Set if the consumer can set the column to a null value or if the provider cannot determine whether the consumer can set the column to a null value. Otherwise, not set. A column may contain null values, even if it cannot be set to a null value.| +|BaseCatalogName|The name of the catalog in the data store that contains the column. NULL if the base catalog name cannot be determined. The default of this column is a null value.| +|BaseColumnName|The name of the column in the data store. This might be different than the column name returned in the ColumnName column if an alias was used. A null value if the base column name cannot be determined or if the rowset column is derived, but not identical to, a column in the data store. The default of this column is a null value.| +|BaseSchemaName|The name of the schema in the data store that contains the column. A null value if the base schema name cannot be determined. The default of this column is a null value.| +|BaseServerName|The name of the instance of Microsoft SQL Server used by the .| +|BaseTableName|The name of the table or view in the data store that contains the column. A null value if the base table name cannot be determined. The default of this column is a null value.| +|ColumnName|The name of the column; this might not be unique. If this cannot be determined, a null value is returned. This name always reflects the most recent renaming of the column in the current view or command text.| +|ColumnOrdinal|The zero-based ordinal of the column. This column cannot contain a null value.| +|ColumnSize|The maximum possible length of a value in the column. For columns that use a fixed-length data type, this is the size of the data type. For `nvarchar(MAX)`, `varchar(MAX)`, and `varbinary(MAX)` columns stored in a SQL Server database, the maximum size is 2GB. If these columns are stored and accessed as files, the limit on maximum size is imposed by the file system. This value changes when using the `Type System Version` keyword in the connection string. For new types they are represented as downlevel types. The MAX data types return the normal 4k for `nvarchar` and 8000 for `varchar`. For more information, see the [Transact-SQL reference](https://learn.microsoft.com/sql/t-sql/language-reference).| +|DataTypeName|Returns a string representing the data type of the specified column.| +|IsAliased|`true`: The column name is an alias.

    `false`: The column name is not an alias.| +|IsAutoIncrement|`true`: The column assigns values to new rows in fixed increments.

    `false`: The column does not assign values to new rows in fixed increments. The default of this column is `false`.| +|IsColumnSet|`true`: The column is a sparse column that is a member of a column set.| +|IsExpression|`true`: The column is an expression.

    `false`: The column is not an expression.| +|IsHidden|`true`: The column is hidden.

    `false`: The column is not hidden.| +|IsIdentity|`true`: The column is an identity column.

    `false`: The column is not an identity column.| +|IsKey|`true`: The column is one of a set of columns in the rowset that, taken together, uniquely identify the row. The set of columns with `IsKey` set to `true` must uniquely identify a row in the rowset. There is no requirement that this set of columns is a minimal set of columns. This set of columns may be generated from a base table primary key, a unique constraint or a unique index.

    `false`: The column is not required to uniquely identify the row.| +|IsLong|`true`: The column contains a Binary Long Object (BLOB) that contains very long data. The definition of very long data is provider-specific.

    `false`: The column does not contain a Binary Long Object (BLOB) that contains very long data.| +|IsReadOnly|`true`: The column cannot be modified.

    `false`: The column can be modified.| +|IsRowVersion|`true`: The column contains a persistent row identifier that cannot be written to, and has no meaningful value except to identity the row.

    `false`: The column does not contain a persistent row identifier that cannot be written to, and has no meaningful value except to identity the row.| +|IsUnique|`true`: Column is of type `timestamp`.

    `false`: Column is not of type `timestamp`.| +|NonVersionedProviderType|The type of the column irrespective of the current `Type System Version` specified in the connection string. The returned value is from the enumeration.| +|NumericPrecision|If `ProviderType` is a numeric data type, this is the maximum precision of the column. The precision depends on the definition of the column. If `ProviderType` is not a numeric data type, this is 255.| +|NumericScale|If `ProviderType` is DBTYPE_DECIMAL or DBTYPE_NUMERIC, the number of digits to the right of the decimal point. Otherwise, this is 255.| +|ProviderSpecificDataType|Returns the provider-specific data type of the column based on the `Type System Version` keyword in the connection string.| +|ProviderType|The indicator of the column's data type. If the data type of the column varies from row to row, this must be Object. This column cannot contain a null value.| +|UdtAssemblyQualifiedName|If the column is a user-defined type (UDT), this is the qualified name of the UDT's assembly as per . If the column is not a UDT, this is null.| +|XmlSchemaCollectionDatabase|The name of the database where the schema collection for this XML instance is located, if the row contains information about an XML column. This value is `null` (`Nothing` in Visual Basic) if the collection is defined within the current database. It is also null if there is no schema collection, in which case the `XmlSchemaCollectionName` and `XmlSchemaCollectionOwningSchema` columns are also null.| +|XmlSchemaCollectionName|The name of the schema collection for this XML instance, if the row contains information about an XML column. This value is `null` (`Nothing` in Visual Basic) if there is no associated schema collection. If the value is null, the `XmlSchemaCollectionDatabase` and `XmlSchemaCollectionOwningSchema` columns are also null.| +|XmlSchemaCollectionOwningSchema|The owning relational schema where the schema collection for this XML instance is located, if the row contains information about an XML column. This value is `null` (`Nothing` in Visual Basic) if the collection is defined within the current database. It is also null if there is no schema collection, in which case the `XmlSchemaCollectionDatabase` and `XmlSchemaCollectionName` columns are also null.| + +> [!NOTE] +> To make sure that metadata columns return the correct information, you must call with the `behavior` parameter set to `KeyInfo`. Otherwise, some of the columns in the schema table may return default, null, or incorrect data. + +]]>
    + The is closed. @@ -1246,6 +956,35 @@ No conversions are performed; therefore, the data retrieved must already be a 64-bit signed integer, or an exception is generated. + + + Gets the value of the specified column as a . + A object representing the column at the given ordinal. + + No conversions are performed; therefore, the data retrieved must already be a JSON string, or an exception is generated. + + + + + + Gets the value of the specified column as a . + + + A object representing the column at the given ordinal. + + + The index passed was outside the range of 0 to - 1 + + + An attempt was made to read or access columns in a closed . + + + The retrieved data is not compatible with the type. + + + No conversions are performed; therefore, the data retrieved must already be a vector value, or an exception is generated. + + The zero-based column ordinal. @@ -1299,7 +1038,7 @@ The value of the column expressed as a . - returns data using the native SQL Server types. To retrieve data using the .NET Framework types, see . + returns data using the native SQL Server types. To retrieve data using the .NET Framework types, see . @@ -1366,15 +1105,15 @@ The following members are not available for objects returned by GetStream:
    - BeginWrite - EndWrite - Length - Position - Seek - SetLength - Write - WriteByte - WriteTimeout + BeginWrite + EndWrite + Length + Position + Seek + SetLength + Write + WriteByte + WriteTimeout For more information, see SqlClient Streaming Support @@ -1382,11 +1121,11 @@ - The connection drops or is closed during the data retrieval. - The is closed during the data retrieval. - There is no data ready to be read (for example, the first hasn't been called, or returned false). - Tried to read a previously-read column in sequential mode. - There was an asynchronous operation in progress. This applies to all Get* methods when running in sequential mode, as they could be called while reading a stream. + The connection drops or is closed during the data retrieval. + The is closed during the data retrieval. + There is no data ready to be read (for example, the first hasn't been called, or returned false). + Tried to read a previously-read column in sequential mode. + There was an asynchronous operation in progress. This applies to all Get* methods when running in sequential mode, as they could be called while reading a stream. @@ -1397,10 +1136,10 @@ The returned type was not one of the types below: - binary - image - varbinary - udt + binary + image + varbinary + udt
    @@ -1449,11 +1188,11 @@ - The connection drops or is closed during the data retrieval. - The is closed during the data retrieval. - There is no data ready to be read (for example, the first hasn't been called, or returned false). - Tried to read a previously-read column in sequential mode. - There was an asynchronous operation in progress. This applies to all Get* methods when running in sequential mode, as they could be called while reading a stream. + The connection drops or is closed during the data retrieval. + The is closed during the data retrieval. + There is no data ready to be read (for example, the first hasn't been called, or returned false). + Tried to read a previously-read column in sequential mode. + There was an asynchronous operation in progress. This applies to all Get* methods when running in sequential mode, as they could be called while reading a stream. @@ -1462,12 +1201,12 @@ The returned type was not one of the types below: - char - nchar - ntext - nvarchar - text - varchar + char + nchar + ntext + nvarchar + text + varchar @@ -1582,7 +1321,7 @@ - The object returned by does not support asynchronous operations. If you require asynchronous operations on an , cast the XML column to an NVARCHAR(MAX) on the server and use with . exceptions raised from are thrown as exceptions; check the inner exception for the . will raise an exception when used on an object returned by when is in effect. + The object returned by does not support asynchronous operations. If you require asynchronous operations on an , cast the XML column to an NVARCHAR(MAX) on the server and use with . exceptions raised from are thrown as exceptions; check the inner exception for the . will raise an exception when used on an object returned by when is in effect. For more information, see SqlClient Streaming Support. @@ -1590,11 +1329,11 @@ - The connection drops or is closed during the data retrieval. - The is closed during the data retrieval. - There is no data ready to be read (for example, the first hasn't been called, or returned false). - Trying to read a previously read column in sequential mode. - There was an asynchronous operation in progress. This applies to all Get* methods when running in sequential mode, as they could be called while reading a stream. + The connection drops or is closed during the data retrieval. + The is closed during the data retrieval. + There is no data ready to be read (for example, the first hasn't been called, or returned false). + Trying to read a previously read column in sequential mode. + There was an asynchronous operation in progress. This applies to all Get* methods when running in sequential mode, as they could be called while reading a stream. @@ -1704,11 +1443,11 @@ - The connection drops or is closed during the data retrieval. - The is closed during the data retrieval. - There is no data ready to be read (for example, the first hasn't been called, or returned false). - Trying to read a previously read column in sequential mode. - There was an asynchronous operation in progress. This applies to all Get* methods when running in sequential mode, as they could be called while reading a stream. + The connection drops or is closed during the data retrieval. + The is closed during the data retrieval. + There is no data ready to be read (for example, the first hasn't been called, or returned false). + Trying to read a previously read column in sequential mode. + There was an asynchronous operation in progress. This applies to all Get* methods when running in sequential mode, as they could be called while reading a stream. @@ -1871,16 +1610,17 @@ A task representing the asynchronous operation. - - If the behavior parameter of is set to Default, ReadAsync reads the entire row before returning the Task. - - - For more information, including code samples, about asynchronous programming in the .NET Framework Data Provider for SQL Server, see Asynchronous Programming. - - - When reading large data (binary/text), it is recommended to use for optimal performance. - - + is set to `Default`, reads the entire row before returning the Task. + +For more information, including code samples, about asynchronous programming in the .NET Framework Data Provider for SQL Server, see [Asynchronous Programming](https://learn.microsoft.com/sql/connect/ado-net/asynchronous-programming). + +> [!NOTE] +> When reading large data (binary/text), it is recommended to use for optimal performance. + +]]> + Calling more than once for the same instance before task completion. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlDependency.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlDependency.xml index b4450f5196..3639ffc53d 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlDependency.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlDependency.xml @@ -5,25 +5,24 @@ The object represents a query notification dependency between an application and an instance of SQL Server. An application can create a object and register to receive notifications via the event handler. - - is ideal for caching scenarios, where your ASP.NET application or middle-tier service needs to keep certain information cached in memory. allows you to receive notifications when the original data in the database changes so that the cache can be refreshed. - - - To set up a dependency, you need to associate a object to one or more objects. To receive notifications, you need to subscribe to the event. For more information about the requirements for creating queries for notifications, see Working with Query Notifications. - - - was designed to be used in ASP.NET or middle-tier services where there is a relatively small number of servers having dependencies active against the database. It was not designed for use in client applications, where hundreds or thousands of client computers would have objects set up for a single database server. If you are developing an application where you need reliable sub-second notifications when data changes, review the sections Planning an Effective Query Notifications Strategy and Alternatives to Query Notifications in the article - - - For more information, see Query Notifications in SQL Server and Building Notification Solutions. - - - The event may be generated on a different thread from the thread that initiated command execution. - - - Query notifications are supported only for SELECT statements that meet a list of specific requirements. - - + is ideal for caching scenarios, where your ASP.NET application or middle-tier service needs to keep certain information cached in memory. allows you to receive notifications when the original data in the database changes so that the cache can be refreshed. + +To set up a dependency, you need to associate a object to one or more objects. To receive notifications, you need to subscribe to the event. For more information about the requirements for creating queries for notifications, see [Working with Query Notifications](https://learn.microsoft.com/sql/relational-databases/native-client/features/working-with-query-notifications). + +> [!NOTE] +> was designed to be used in ASP.NET or middle-tier services where there is a relatively small number of servers having dependencies active against the database. It was not designed for use in client applications, where hundreds or thousands of client computers would have objects set up for a single database server. If you are developing an application where you need reliable sub-second notifications when data changes, review the sections [Planning an Efficient Query Notifications Strategy](https://learn.microsoft.com/previous-versions/sql/sql-server-2008-r2/ms187528(v=sql.105)#planning-an-efficient-query-notifications-strategy) and [Alternatives to Query Notifications](https://learn.microsoft.com/previous-versions/sql/sql-server-2008-r2/ms187528(v=sql.105)#alternatives-to-query-notifications) in the [Planning for Notifications](https://learn.microsoft.com/previous-versions/sql/sql-server-2008-r2/ms187528(v=sql.105)) article. + +For more information, see [Query Notifications in SQL Server](https://learn.microsoft.com//sql/connect/ado-net/sql/query-notifications-sql-server) and [Building Notification Solutions](https://learn.microsoft.com/previous-versions/sql/sql-server-2005/ms171065(v=sql.90)). + +> [!NOTE] +> The event may be generated on a different thread from the thread that initiated command execution. + +Query notifications are supported only for SELECT statements that meet a list of specific requirements. + +]]> + @@ -84,12 +83,12 @@ - + The object already has a object assigned to its property and that is not associated with this dependency. - - + + An attempt was made to create a instance from within SQLCLR. - + @@ -163,18 +162,18 @@ This method starts the listener for the for receiving dependency notifications from the instance of SQL Server specified by the parameter. This method may be called more than once with different connection strings for multiple servers. - Multiple calls to the method can be made, subject to the following restrictions: + Multiple calls to the method can be made, subject to the following restrictions: - Multiple calls with identical parameters (the same connection string and Windows credentials in the calling thread) are valid. - + Multiple calls with identical parameters (the same connection string and Windows credentials in the calling thread) are valid. + Multiple calls with different connection strings are valid as long as: - Each connection string specifies a different database, or - Each connection string specifies a different user, or - The calls come from different application domains. + Each connection string specifies a different database, or + Each connection string specifies a different user, or + The calls come from different application domains. - + You can make the work correctly for applications that use multiple threads to represent different user credentials without giving the dbo role to the group, because different users can subscribe and listen (using or ) to a notification queue created by an administrator. When the relevant application domain starts, call Start with the (Windows) credentials of a user that has permission to initialize a service/queue (the CREATE QUEUE and CREATE SERVICE permissions for the database). Ensure that Start is only called once per AppDomain, otherwise an ambiguity exception is raised. The user thread must have permission to subscribe to the notification (the SUBSCRIBE QUERY NOTIFICATIONS permission for the database). will associate the subscription request of a non-administrator user to the service/queue created by the administrator. @@ -185,8 +184,8 @@ - The parameter is the same as a previous call to this method, but the parameters are different. - The method was called from within the CLR. + The parameter is the same as a previous call to this method, but the parameters are different. + The method was called from within the CLR. @@ -216,18 +215,18 @@ If no queue name is specified, creates a temporary queue and service in the server that is used for the entire process, even if the process involves more than one . The queue and service are automatically removed upon application shutdown. - Multiple calls to the method can be made, subject to the following restrictions: + Multiple calls to the method can be made, subject to the following restrictions: - Multiple calls with identical parameters (the same connection string and Windows credentials in the calling thread) are valid. - + Multiple calls with identical parameters (the same connection string and Windows credentials in the calling thread) are valid. + Multiple calls with different connection strings are valid as long as: - Each connection string specifies a different database, or - Each connection string specifies a different user, or - The calls come from different application domains. + Each connection string specifies a different database, or + Each connection string specifies a different user, or + The calls come from different application domains. - + You can make the work correctly for applications that use multiple threads to represent different user credentials without giving the dbo role to the group, because different users can subscribe and listen (using or ) to a notification queue created by an administrator. When the relevant application domain starts, call Start with the (Windows) credentials of a user that has permission to initialize a service/queue (the CREATE QUEUE and CREATE SERVICE permissions for the database). Ensure that Start is only called once per AppDomain, otherwise an ambiguity exception is raised. The user thread must have permission to subscribe to the notification (the SUBSCRIBE QUERY NOTIFICATIONS permission for the database). will associate the subscription request of a non-administrator user to the service/queue created by the administrator. @@ -238,8 +237,8 @@ - The parameter is the same as a previous call to this method, but the parameters are different. - The method was called from within the CLR. + The parameter is the same as a previous call to this method, but the parameters are different. + The method was called from within the CLR. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlParameter.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlParameter.xml index 22a1d92603..ae7dbdd931 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlParameter.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlParameter.xml @@ -5,58 +5,22 @@ Represents a parameter to a and optionally its mapping to columns. This class cannot be inherited. For more information on parameters, see Configuring parameters - - Parameter names are not case-sensitive. - - - Nameless, also called ordinal, parameters are not supported by the .NET Framework Data Provider for SQL Server. - - - For more information, along with additional sample code demonstrating how to use parameters, see Commands and Parameters. - + [!NOTE] +> Nameless, also called ordinal, parameters are not supported by the .NET Framework Data Provider for SQL Server. + +For more information, along with additional sample code demonstrating how to use parameters, see [Commands and Parameters](https://learn.microsoft.com/sql/connect/ado-net/commands-parameters). + +## Examples +The following example creates multiple instances of through the collection within the . These parameters are used to select data from the data source and put the data in the . This example assumes that a and a have already been created by using the appropriate schema, commands, and connection. For more information and additional examples on using parameters, see [Retrieving and Modifying Data in ADO.NET](https://learn.microsoft.com//sql/connect/ado-net/retrieving-modifying-data) and [Configuring parameters](https://learn.microsoft.com//sql/connect/ado-net/configure-parameters). + +[!code-csharp[SqlParameterCollection_Add6](~/../sqlclient/doc/samples/SqlParameterCollection_Add6.cs#1)] + +]]> - - - The following example creates multiple instances of through the collection within the . These parameters are used to select data from the data source and put the data in the . - - - This example assumes that a and a have already been created by using the appropriate schema, commands, and connection. For more information and additional examples on using parameters, see Retrieving and Modifying Data in ADO.NET and Configuring parameters. - - - - using System; - using System.Data; - using System.Data.Common; - using System.Windows.Forms; - using System.Xml; - using Microsoft.Data.SqlClient; - - public class Form1 : Form - { - protected DataSet categoriesDataSet; - protected DataGrid dataGrid1; - protected SqlDataAdapter categoriesAdapter; - - public void AddSqlParameters() - { - // ... - // create categoriesDataSet and categoriesAdapter - // ... - - categoriesAdapter.SelectCommand.Parameters.Add( - "@CategoryName", - SqlDbType.VarChar, - 80 - ).Value = "toasters"; - categoriesAdapter.SelectCommand.Parameters.Add( - "@SerialNum", - SqlDbType.Int - ).Value = 239; - categoriesAdapter.Fill(categoriesDataSet); - } - } - - @@ -476,16 +440,16 @@ One of the values. The default is . - If the is output, and execution of the associated does not return a value, the contains a null value. , , and parameters returned by calling cannot be accessed until you close the . + If the is output, and execution of the associated does not return a value, the contains a null value. , , and parameters returned by calling cannot be accessed until you close the . The following example creates a and sets some of its properties. - Commands and Parameters - DataAdapter Parameters - SQL Server and ADO.NET + Commands and Parameters + DataAdapter Parameters + SQL Server and ADO.NET @@ -615,43 +579,26 @@ The maximum number of digits used to represent the property. The default value is 0. This indicates that the data provider sets the precision for . - - The Precision property is used by parameters that have a of Decimal. - - - You do not need to specify values for the Precision and properties for input parameters, as they can be inferred from the parameter value. Precision and are required for output parameters and for scenarios where you need to specify complete metadata for a parameter without indicating a value, such as specifying a null value with a specific precision and scale. - - - Use of this property to coerce data passed to the database is not supported. To round, truncate, or otherwise coerce data before passing it to the database, use the class that is part of the System namespace prior to assigning a value to the parameter's property. - - - Microsoft .NET Framework data providers that are included with the .NET Framework version 1.0 do not verify the Precision or of parameter values. This can cause truncated data being inserted at the data source. If you are using .NET Framework version 1.0, validate the Precision and of values before setting the parameter value. When you use .NET Framework version 1.1 or a later version, an exception is thrown if a parameter value is set with an invalid Precision. values that exceed the parameter scale are still truncated. - + property is used by parameters that have a of `Decimal`. + +You do not need to specify values for the and properties for input parameters, as they can be inferred from the parameter value. `Precision` and `Scale` are required for output parameters and for scenarios where you need to specify complete metadata for a parameter without indicating a value, such as specifying a null value with a specific precision and scale. + +> [!NOTE] +> Use of this property to coerce data passed to the database is not supported. To round, truncate, or otherwise coerce data before passing it to the database, use the class that is part of the `System` namespace prior to assigning a value to the parameter's `Value` property. + +> [!NOTE] +> Microsoft .NET Framework data providers that are included with the .NET Framework version 1.0 do not verify the or of parameter values. This can cause truncated data being inserted at the data source. If you are using .NET Framework version 1.0, validate the and of values before setting the parameter value. When you use .NET Framework version 1.1 or a later version, an exception is thrown if a parameter value is set with an invalid . values that exceed the parameter scale are still truncated. + +## Examples +The following example creates a and sets some of its properties. + +[!code-csharp[SqlParameter_Precision Example#1](~/../sqlclient/doc/samples/SqlParameter_Precision.cs#1)] + +]]> - - - The following example creates a and sets some of its properties. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - class Program - { - private static void AddSqlParameter(SqlCommand command) - { - SqlParameter parameter = new SqlParameter("@Price", SqlDbType.Decimal); - parameter.Value = 3.1416; - parameter.Precision = 8; - parameter.Scale = 4; - - command.Parameters.Add(parameter); - } - } - - @@ -677,22 +624,28 @@ The number of decimal places to which is resolved. The default is 0. - - The Scale property is used by parameters that have a of Decimal. - - - Data may be truncated if the Scale property is not explicitly specified and the data on the server does not fit in scale 0 (the default). - - - You do not need to specify values for the and Scale properties for input parameters, as they can be inferred from the parameter value. and Scale are required for output parameters and for scenarios where you need to specify complete metadata for a parameter without indicating a value, such as specifying a null value with a specific precision and scale. - - - Use of this property to coerce data passed to the database is not supported. To round, truncate, or otherwise coerce data before passing it to the database, use the class that is part of the System namespace prior to assigning a value to the parameter's property. - - - .NET Framework data providers that are included with the .NET Framework version 1.0 do not verify the or Scale of parameter values. This can cause truncated data to be inserted at the data source. If you are using .NET Framework version 1.0, validate the and of values before setting the parameter value. Scale values that exceed the parameter scale are still truncated. - - + property is used by parameters that have a of `Decimal`. + +> [!WARNING] +> Data may be truncated if the property is not explicitly specified and the data on the server does not fit in scale 0 (the default). + +You do not need to specify values for the and properties for input parameters, as they can be inferred from the parameter value. `Precision` and `Scale` are required for output parameters and for scenarios where you need to specify complete metadata for a parameter without indicating a value, such as specifying a null value with a specific precision and scale. + +> [!NOTE] +> Use of this property to coerce data passed to the database is not supported. To round, truncate, or otherwise coerce data before passing it to the database, use the class that is part of the `System` namespace prior to assigning a value to the parameter's `Value` property. + +> [!NOTE] +> .NET Framework data providers that are included with the .NET Framework version 1.0 do not verify the or of parameter values. This can cause truncated data to be inserted at the data source. If you are using .NET Framework version 1.0, validate the and of values before setting the parameter value. values that exceed the parameter scale are still truncated. + +## Examples +The following example creates a and sets some of its properties. + +[!code-csharp[SqlParameter#4](~/../sqlclient/doc/samples/SqlParameter.cs#4)] + +]]> + The following example creates a and sets some of its properties. @@ -846,7 +799,7 @@ Gets or sets the of the parameter. - One of the values. The default is . + One of the values. The default is . diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlRetryLogicBase.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlRetryLogicBase.xml index 3557cb287e..62b3425f48 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlRetryLogicBase.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlRetryLogicBase.xml @@ -48,12 +48,14 @@ Returns if the sender is authorized to retry the operation. - - Operations that are part of a Transaction are not safe to retry without specific knowledge of business logic. Due to this complexity, retry logic should be managed at the application level. - - - The RetryCondition is an extra condition that checks before executing the and the default condition always returns . - + + [!IMPORTANT] +> Operations that are part of a **Transaction** are not safe to retry without specific knowledge of business logic. Due to this complexity, retry logic should be managed at the application level. + +> [!NOTE] +> The `RetryCondition` is an extra condition that checks before executing the `TransientPredicate` and the default condition always returns **true**. +]]> diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlRetryLogicBaseProvider.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlRetryLogicBaseProvider.xml index 72bd0d7ced..dd44b411f8 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlRetryLogicBaseProvider.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlRetryLogicBaseProvider.xml @@ -2,7 +2,7 @@ - Applies retry logic on an operation through the or function. + Applies retry logic on an operation through the or function. @@ -13,9 +13,12 @@ with event argument of object can be subscribed. - - Don't block execution with a time-consuming action when an event occurs. For instance, if you log data to a file, run it in a new thread to avoid blocking the main execution thread. - + + [!IMPORTANT] +> Don't block execution with a time-consuming action when an event occurs. For instance, if you log data to a file, run it in a new thread to avoid blocking the main execution thread. + ]]> + @@ -23,9 +26,12 @@ Defines the retry logic used to decide when to retry based on the encountered exception. - - The RetryLogic property is assigned at construction of the creation and its value is used as a template internally. Don't use it to monitor the status of the retry logic during and after execution. Instead, use the event to collect data about retry executions. - + + [!NOTE] +> The **RetryLogic** property is assigned at construction of the creation and its value is used as a template internally. Don't use it to monitor the status of the retry logic during and after execution. Instead, use the event to collect data about retry executions. + ]]> + @@ -39,22 +45,18 @@ The operation to re-execute if a transient condition occurs. - - Executes a function and applies retry logic, if enabled. - - - Exceptions will be reported via an aggregate exception if the execution isn't successful via retry attempts. - + Executes a function and applies retry logic, if enabled. **Note:** Exceptions will be reported via an aggregate exception if the execution isn't successful via retry attempts. The return value of the if it runs without exception. - - - The type of exception depends on the 's internal implementation. But if the exception is due to all retry attempts failing, it will be an that consists of all exceptions that happened during the failed attempts. - - + + [!NOTE] +> The type of exception depends on the `function`'s internal implementation. But if the exception is due to all retry attempts failing, it will be an that consists of all exceptions that happened during the failed attempts. + ]]> + The parameter can't be . @@ -77,20 +79,18 @@ The cancellation instruction. - - Executes a function and applies retry logic, if enabled. The cancellation token can be used to request that the operation be abandoned before the execution attempts are exceeded. - - - Exceptions will be reported via the returned Task object, which will contain an aggregate exception if execution fails for all retry attempts. - + Executes a function and applies retry logic, if enabled. The cancellation token can be used to request that the operation be abandoned before the execution attempts are exceeded. **Note:** Exceptions will be reported via the returned Task object, which will contain an aggregate exception if execution fails for all retry attempts. A task representing the asynchronous operation. The results of the task will be the return value of the , if it runs without exception. - - If the exception comes from all retry attempts failing, it will be an that consists of all exceptions from the failed attempts. - + + [!NOTE] +> If the exception comes from all retry attempts failing, it will be an that consists of all exceptions from the failed attempts. + ]]> + The parameter can't be . @@ -110,20 +110,18 @@ The cancellation instruction. - - Executes a function and applies retry logic, if enabled. The cancellation token can be used to request that the operation be abandoned before the execution attempts are exceeded. - - - Exceptions will be reported via the returned Task object, which will contain an aggregate exception if execution fails for all retry attempts. - + Executes a function and applies retry logic, if enabled. The cancellation token can be used to request that the operation be abandoned before the execution attempts are exceeded. ** Note:** Exceptions will be reported via the returned Task object, which will contain an aggregate exception if execution fails for all retry attempts. A Task or an exception. - - If the exception comes from all retry attempts failing, it will be an that consists of all exceptions from the failed attempts. - + + [!NOTE] +> If the exception comes from all retry attempts failing, it will be an that consists of all exceptions from the failed attempts. + ]]> + The parameter can't be . diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlRowUpdatedEventArgs.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlRowUpdatedEventArgs.xml index f0d95737a1..4e7e45f95a 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlRowUpdatedEventArgs.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlRowUpdatedEventArgs.xml @@ -12,13 +12,13 @@ When using , there are two events that occur for each data row updated. The order of execution is as follows: - The values in the are moved to the parameter values. - The event is raised. - The command executes. - If the command is set to FirstReturnedRecord, and the first returned result is placed in the . - If there are output parameters, they are placed in the . - The event is raised. - is called. + The values in the are moved to the parameter values. + The event is raised. + The command executes. + If the command is set to FirstReturnedRecord, and the first returned result is placed in the . + If there are output parameters, they are placed in the . + The event is raised. + is called. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlRowUpdatingEventArgs.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlRowUpdatingEventArgs.xml index e1a9ffe8e5..ee545db905 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlRowUpdatingEventArgs.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlRowUpdatingEventArgs.xml @@ -6,19 +6,19 @@ - The event is raised before an to a row. + The event is raised before an to a row. When you are using , there are two events that occur for each data row updated. The order of execution is as follows: - The values in the are moved to the parameter values. - The event is raised. - The command executes. - If the command is set to FirstReturnedRecord, and the first returned result is placed in the . - If there are output parameters, they are placed in the . - The event is raised. - is called. + The values in the are moved to the parameter values. + The event is raised. + The command executes. + If the command is set to FirstReturnedRecord, and the first returned result is placed in the . + If there are output parameters, they are placed in the . + The event is raised. + is called. diff --git a/doc/snippets/Microsoft.Data.SqlClient/SqlTransaction.xml b/doc/snippets/Microsoft.Data.SqlClient/SqlTransaction.xml index 9329b62e68..c05cc8b976 100644 --- a/doc/snippets/Microsoft.Data.SqlClient/SqlTransaction.xml +++ b/doc/snippets/Microsoft.Data.SqlClient/SqlTransaction.xml @@ -5,193 +5,51 @@ Represents a Transact-SQL transaction to be made in a SQL Server database. This class cannot be inherited. - - The application creates a object by calling on the object. All subsequent operations associated with the transaction (for example, committing or aborting the transaction), are performed on the object. - - - Try / Catch exception handling should always be used when committing or rolling back a . Both and generate an if the connection is terminated or if the transaction has already been rolled back on the server. - - - For more information on SQL Server transactions, see Explicit Transactions - + object by calling on the object. All subsequent operations associated with the transaction (for example, committing or aborting the transaction), are performed on the object. + +> [!NOTE] +> `Try`/`Catch` exception handling should always be used when committing or rolling back a . Both and generate an if the connection is terminated or if the transaction has already been rolled back on the server. + +For more information on SQL Server transactions, see [Explicit Transactions](https://learn.microsoft.com/sql/t-sql/language-elements/transactions-transact-sql) + +## Examples + The following example creates a and a . It also demonstrates how to use the , , and methods. The transaction is rolled back on any error, or if it is disposed without first being committed. `Try`/`Catch` error handling is used to handle any errors when attempting to commit or roll back the transaction. + + [!code-csharp[SqlConnection_BeginTransaction Example#1](~/../sqlclient/doc/samples/SqlConnection_BeginTransaction.cs#1)] + + ]]> - - - The following example creates a and a . It also demonstrates how to use the , , and methods. The transaction is rolled back on any error, or if it is disposed without first being committed. Try / Catch error handling is used to handle any errors when attempting to commit or roll back the transaction. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - namespace Transaction1CS - { - class Program - { - static void Main() - { - string connectionString = - "Persist Security Info=False;Integrated Security=SSPI;database=Northwind;server=(local)"; - ExecuteSqlTransaction(connectionString); - Console.ReadLine(); - } - - private static void ExecuteSqlTransaction(string connectionString) - { - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - SqlCommand command = connection.CreateCommand(); - SqlTransaction transaction; - - // Start a local transaction. - transaction = connection.BeginTransaction(); - - // Must assign both transaction object and connection - // to Command object for a pending local transaction - command.Connection = connection; - command.Transaction = transaction; - - try - { - command.CommandText = - "Insert into Region (RegionID, RegionDescription) VALUES (100, 'Description')"; - command.ExecuteNonQuery(); - command.CommandText = - "Insert into Region (RegionID, RegionDescription) VALUES (101, 'Description')"; - command.ExecuteNonQuery(); - - // Attempt to commit the transaction. - transaction.Commit(); - Console.WriteLine("Both records are written to database."); - } - catch (Exception ex) - { - Console.WriteLine("Commit Exception Type: {0}", ex.GetType()); - Console.WriteLine(" Message: {0}", ex.Message); - - // Attempt to roll back the transaction. - try - { - transaction.Rollback(); - } - catch (Exception ex2) - { - // This catch block will handle any errors that may have occurred - // on the server that would cause the rollback to fail, such as - // a closed connection. - Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType()); - Console.WriteLine(" Message: {0}", ex2.Message); - } - } - } - } - } - } - - Commits the database transaction. - - The method is equivalent to the Transact-SQL COMMIT TRANSACTION statement. You cannot roll back a transaction once it has been committed, because all modifications have become a permanent part of the database. For more information, see COMMIT TRANSACTION (Transact-SQL). - - - Try / Catch exception handling should always be used when committing or rolling back a . Both Commit and generates an if the connection is terminated or if the transaction has already been rolled back on the server. - - - For more information on SQL Server transactions, see Transactions (Transact-SQL). - - - - - The following example creates a and a . It also demonstrates how to use the , , and methods. The transaction is rolled back on any error. Try / Catch error handling is used to handle any errors when attempting to commit or roll back the transaction. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - namespace Transaction1CS - { - class Program - { - static void Main() - { - string connectionString = - "Persist Security Info=False;Integrated Security=SSPI;database=Northwind;server=(local)"; - ExecuteSqlTransaction(connectionString); - Console.ReadLine(); - } - - private static void ExecuteSqlTransaction(string connectionString) - { - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - SqlCommand command = connection.CreateCommand(); - SqlTransaction transaction; - - // Start a local transaction. - transaction = connection.BeginTransaction(); - - // Must assign both transaction object and connection - // to Command object for a pending local transaction - command.Connection = connection; - command.Transaction = transaction; - - try - { - command.CommandText = - "Insert into Region (RegionID, RegionDescription) VALUES (100, 'Description')"; - command.ExecuteNonQuery(); - command.CommandText = - "Insert into Region (RegionID, RegionDescription) VALUES (101, 'Description')"; - command.ExecuteNonQuery(); - - // Attempt to commit the transaction. - transaction.Commit(); - Console.WriteLine("Both records are written to database."); - } - catch (Exception ex) - { - Console.WriteLine("Commit Exception Type: {0}", ex.GetType()); - Console.WriteLine(" Message: {0}", ex.Message); - - // Attempt to roll back the transaction. - try - { - transaction.Rollback(); - } - catch (Exception ex2) - { - // This catch block will handle any errors that may have occurred - // on the server that would cause the rollback to fail, such as - // a closed connection. - Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType()); - Console.WriteLine(" Message: {0}", ex2.Message); - } - } - } - } - } - } - - + method is equivalent to the Transact-SQL COMMIT TRANSACTION statement. You cannot roll back a transaction once it has been committed, because all modifications have become a permanent part of the database. For more information, see [COMMIT TRANSACTION (Transact-SQL)](https://learn.microsoft.com/sql/t-sql/language-elements/commit-transaction-transact-sql). + +> [!NOTE] +> `Try`/`Catch` exception handling should always be used when committing or rolling back a . Both `Commit` and generates an if the connection is terminated or if the transaction has already been rolled back on the server. + +For more information on SQL Server transactions, see [Transactions (Transact-SQL)](https://learn.microsoft.com/sql/t-sql/language-elements/transactions-transact-sql). + +## Examples +The following example creates a and a . It also demonstrates how to use the , , and methods. The transaction is rolled back on any error. `Try`/`Catch` error handling is used to handle any errors when attempting to commit or roll back the transaction. + +[!code-csharp[SqlConnection_BeginTransaction Example#1](~/../sqlclient/doc/samples/SqlConnection_BeginTransaction.cs#1)] + + ]]> + An error occurred while trying to commit the transaction. - The transaction has already been committed or rolled back. - The connection is broken. + The transaction has already been committed or rolled back. + The connection is broken. @@ -259,103 +117,31 @@ Rolls back a transaction from a pending state. - - The Rollback method is equivalent to the Transact-SQL ROLLBACK TRANSACTION statement. For more information, see ROLLBACK TRANSACTION (Transact-SQL). - - - The transaction can only be rolled back from a pending state (after has been called, but before is called). The transaction is rolled back in the event it is disposed before or Rollback is called. - - - Try / Catch exception handling should always be used when rolling back a transaction. A Rollback generates an if the connection is terminated or if the transaction has already been rolled back on the server. - - - For more information on SQL Server transactions, see Transactions (Transact-SQL). - + method is equivalent to the Transact-SQL ROLLBACK TRANSACTION statement. For more information, see [ROLLBACK TRANSACTION (Transact-SQL)](https://learn.microsoft.com/sql/t-sql/language-elements/rollback-transaction-transact-sql). + +The transaction can only be rolled back from a pending state (after has been called, but before is called). The transaction is rolled back in the event it is disposed before `Commit` or `Rollback` is called. + +> [!NOTE] +> `Try`/`Catch` exception handling should always be used when rolling back a transaction. A `Rollback` generates an if the connection is terminated or if the transaction has already been rolled back on the server. + +For more information on SQL Server transactions, see [Transactions (Transact-SQL)](https://learn.microsoft.com/sql/t-sql/language-elements/transactions-transact-sql). + +## Examples +The following example creates a and a . It also demonstrates how to use the , , and methods. The transaction is rolled back on any error. `Try`/`Catch` error handling is used to handle any errors when attempting to commit or roll back the transaction. + +[!code-csharp[SqlConnection_BeginTransaction Example#1](~/../sqlclient/doc/samples/SqlConnection_BeginTransaction.cs#1)] + +]]> - - - The following example creates a and a . It also demonstrates how to use the , , and methods. The transaction is rolled back on any error. Try / Catch error handling is used to handle any errors when attempting to commit or roll back the transaction. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - namespace Transaction1CS - { - class Program - { - static void Main() - { - string connectionString = - "Persist Security Info=False;Integrated Security=SSPI;database=Northwind;server=(local)"; - ExecuteSqlTransaction(connectionString); - Console.ReadLine(); - } - - private static void ExecuteSqlTransaction(string connectionString) - { - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - SqlCommand command = connection.CreateCommand(); - SqlTransaction transaction; - - // Start a local transaction. - transaction = connection.BeginTransaction(); - - // Must assign both transaction object and connection - // to Command object for a pending local transaction - command.Connection = connection; - command.Transaction = transaction; - - try - { - command.CommandText = - "Insert into Region (RegionID, RegionDescription) VALUES (100, 'Description')"; - command.ExecuteNonQuery(); - command.CommandText = - "Insert into Region (RegionID, RegionDescription) VALUES (101, 'Description')"; - command.ExecuteNonQuery(); - - // Attempt to commit the transaction. - transaction.Commit(); - Console.WriteLine("Both records are written to database."); - } - catch (Exception ex) - { - Console.WriteLine("Commit Exception Type: {0}", ex.GetType()); - Console.WriteLine(" Message: {0}", ex.Message); - - // Attempt to roll back the transaction. - try - { - transaction.Rollback(); - } - catch (Exception ex2) - { - // This catch block will handle any errors that may have occurred - // on the server that would cause the rollback to fail, such as - // a closed connection. - Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType()); - Console.WriteLine(" Message: {0}", ex2.Message); - } - } - } - } - } - } - - An error occurred while trying to commit the transaction. - The transaction has already been committed or rolled back. - The connection is broken. + The transaction has already been committed or rolled back. + The connection is broken. @@ -367,103 +153,31 @@ Rolls back a transaction from a pending state, and specifies the transaction or savepoint name. - - The method is equivalent to the Transact-SQL ROLLBACK TRANSACTION statement. For more information, see Transactions (Transact-SQL). - - - The transaction can only be rolled back from a pending state (after has been called, but before is called). The transaction is rolled back if it is disposed before or Rollback is called. - - - Try / Catch exception handling should always be used when rolling back a transaction. A Rollback generates an if the connection is terminated or if the transaction has already been rolled back on the server. - - - For more information on SQL Server transactions, see Transactions (Transact-SQL). - - - - - The following example creates a and a . It also demonstrates how to use the , , and methods. The transaction is rolled back on any error. Try / Catch error handling is used to handle any errors when attempting to commit or roll back the transaction. - - - - using System; - using System.Data; - using Microsoft.Data.SqlClient; - - namespace Transaction1CS - { - class Program - { - static void Main() - { - string connectionString = - "Persist Security Info=False;Integrated Security=SSPI;database=Northwind;server=(local)"; - ExecuteSqlTransaction(connectionString); - Console.ReadLine(); - } + method is equivalent to the Transact-SQL ROLLBACK TRANSACTION statement. For more information, see [Transactions (Transact-SQL)](https://learn.microsoft.com/sql/t-sql/language-elements/transactions-transact-sql). + +The transaction can only be rolled back from a pending state (after has been called, but before is called). The transaction is rolled back if it is disposed before `Commit` or `Rollback` is called. - private static void ExecuteSqlTransaction(string connectionString) - { - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - - SqlCommand command = connection.CreateCommand(); - SqlTransaction transaction; - - // Start a local transaction. - transaction = connection.BeginTransaction("SampleTransaction"); - - // Must assign both transaction object and connection - // to Command object for a pending local transaction - command.Connection = connection; - command.Transaction = transaction; - - try - { - command.CommandText = - "Insert into Region (RegionID, RegionDescription) VALUES (100, 'Description')"; - command.ExecuteNonQuery(); - command.CommandText = - "Insert into Region (RegionID, RegionDescription) VALUES (101, 'Description')"; - command.ExecuteNonQuery(); - - // Attempt to commit the transaction. - transaction.Commit(); - Console.WriteLine("Both records are written to database."); - } - catch (Exception ex) - { - Console.WriteLine("Commit Exception Type: {0}", ex.GetType()); - Console.WriteLine(" Message: {0}", ex.Message); - - // Attempt to roll back the transaction. - try - { - transaction.Rollback("SampleTransaction"); - } - catch (Exception ex2) - { - // This catch block will handle any errors that may have occurred - // on the server that would cause the rollback to fail, such as - // a closed connection. - Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType()); - Console.WriteLine(" Message: {0}", ex2.Message); - } - } - } - } - } - } - - +> [!NOTE] +> `Try`/`Catch` exception handling should always be used when rolling back a transaction. A `Rollback` generates an if the connection is terminated or if the transaction has already been rolled back on the server. + +For more information on SQL Server transactions, see [Transactions (Transact-SQL)](https://learn.microsoft.com/sql/t-sql/language-elements/transactions-transact-sql). + +## Examples +The following example creates a and a . It also demonstrates how to use the , , and methods. The transaction is rolled back on any error. `Try`/`Catch` error handling is used to handle any errors when attempting to commit or roll back the transaction. + +[!code-csharp[SqlConnection_BeginTransaction2 Example#1](~/../sqlclient/doc/samples/SqlConnection_BeginTransaction2.cs#1)] + +]]> + No transaction name was specified. - The transaction has already been committed or rolled back. - The connection is broken. + The transaction has already been committed or rolled back. + The connection is broken. @@ -490,8 +204,8 @@ - The transaction has already been committed or rolled back. - The connection is broken. + The transaction has already been committed or rolled back. + The connection is broken. diff --git a/doc/snippets/Microsoft.Data.SqlTypes/SqlFileStream.xml b/doc/snippets/Microsoft.Data.SqlTypes/SqlFileStream.xml index 4687ea7c1d..cffed27ca3 100644 --- a/doc/snippets/Microsoft.Data.SqlTypes/SqlFileStream.xml +++ b/doc/snippets/Microsoft.Data.SqlTypes/SqlFileStream.xml @@ -5,24 +5,22 @@ Exposes SQL Server data that is stored with the FILESTREAM column attribute as a sequence of bytes. - - The class is used to work with VARBINARY(MAX) data stored with the FILESTREAM attribute in a SQL Server 2008 database. You must install the .NET Framework 3.5 SP1 (or later) to use to work with FILESTREAM data. - - - Specifying the FILESTREAM attribute on a VARBINARY(MAX) column causes SQL Server to store the data in the local NTFS file system instead of in the database file. Transact-SQL statements provide data manipulation capabilities within the server, and Win32 file system interfaces provide streaming access to the data. - - - Individual files stored in a FILESTREAM column cannot be opened directly from the NTFS file system. Streaming FILESTREAM data works only in the context of a SQL Server transaction. - - - The class is derived from the class, which represents an abstraction of a sequence of bytes from some arbitrary data source such as a file or a block of memory. You can read from a FILESTREAM by transferring data from a stream into a data structure such as an array of bytes. You can write to a FILESTREAM by transferring the data from a data structure into a stream. You can also seek within the stream, which allows you to query and modify data at the current position within the stream. - - - For conceptual documentation and code examples, see FILESTREAM Data. - - - For documentation about setting up and configuring FILESTREAM data on SQL Server, see Designing and Implementing FILESTREAM Storage in SQL Server 2008 Books Online. - + + class is used to work with `varbinary(max)` data stored with the FILESTREAM attribute in a SQL Server 2008 database. You must install the .NET Framework 3.5 SP1 (or later) to use to work with FILESTREAM data. + +Specifying the FILESTREAM attribute on a `varbinary(max)` column causes SQL Server to store the data in the local NTFS file system instead of in the database file. Transact-SQL statements provide data manipulation capabilities within the server, and Win32 file system interfaces provide streaming access to the data. + +> [!NOTE] +> Individual files stored in a FILESTREAM column cannot be opened directly from the NTFS file system. Streaming FILESTREAM data works only in the context of a SQL Server transaction. + +The class is derived from the class, which represents an abstraction of a sequence of bytes from some arbitrary data source such as a file or a block of memory. You can read from a FILESTREAM by transferring data from a stream into a data structure such as an array of bytes. You can write to a FILESTREAM by transferring the data from a data structure into a stream. You can also seek within the stream, which allows you to query and modify data at the current position within the stream. + +For conceptual documentation and code examples, see [FILESTREAM Data](https://learn.microsoft.com/sql/connect/ado-net/sql/filestream-data). + +For documentation about setting up and configuring FILESTREAM data on SQL Server, see [Designing and Implementing FILESTREAM Storage](https://go.microsoft.com/fwlink/?LinkId=121499) in SQL Server 2008 Books Online. + +]]> FILESTREAM Data in SQL Server 2008 (ADO.NET) @@ -52,7 +50,7 @@ When using , points to a zero byte file. Existing data will be overwritten when the object is closed and the transaction is committed. - When using , the points to a file which has all the existing data in it. The handle is positioned at the beginning of the file. You can use one of the methods to move the handle position within the file to write or append new data. + When using , the points to a file which has all the existing data in it. The handle is positioned at the beginning of the file. You can use one of the methods to move the handle position within the file to write or append new data. @@ -132,7 +130,7 @@ The transaction context for the object. When set to null, an implicit transaction will be used for the object. Applications should return the byte array returned by calling the GET_FILESTREAM_TRANSACTION_CONTEXT method. - The access mode to use when opening the file. Supported enumeration values are , , and . When using , the object can be used to read all the existing data. When using , points to a zero byte file. Existing data will be overwritten when the object is closed and the transaction is committed. When using , the points to a file which has all the existing data in it. The handle is positioned at the beginning of the file. You can use one of the methods to move the handle position within the file to write or append new data. + The access mode to use when opening the file. Supported enumeration values are , , and . When using , the object can be used to read all the existing data. When using , points to a zero byte file. Existing data will be overwritten when the object is closed and the transaction is committed. When using , the points to a file which has all the existing data in it. The handle is positioned at the beginning of the file. You can use one of the methods to move the handle position within the file to write or append new data. Specifies the option to use while opening the file. Supported values are , , , and . diff --git a/doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml b/doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml index 6d55346417..fa02584fb3 100644 --- a/doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml +++ b/doc/snippets/Microsoft.Data.SqlTypes/SqlJson.xml @@ -1,33 +1,69 @@ - + Represents the JSON datatype in SQL Server. - Parameterless constructor. Initializes a new instance of the SqlJson class which represents a null JSON value. + + Construct a new instance of the SqlJson class which represents a null + JSON value. + - - Takes a as input and initializes a new instance of the SqlJson class. + + Construct a new instance of the SqlJson class with a serialized JSON + . The string is validated by parsing it with + . + + + The serialized JSON string to use, or null. + + + + If the given string is not valid JSON. + + - - Takes a as input and initializes a new instance of the SqlJson class. + + Construct a new instance of the SqlJson class with a + . The serialized JSON string + from the document is saved. + + + The document to use, or null. + + + + If the given document has been disposed of. + + - Represents a null instance of the type. + + Represents a null instance of the type. This + instance is equivalent to calling the parameterless constructor, or + calling the other constructors with a null value. + - Gets the string representation of the Json content of this instance. + + Gets the serialized JSON string of this instance. + + + + If the JSON value is null. + + - - - Retrieves the column at ordinal as a . - A object representing the column at the given ordinal. - + + + Returns the serialized JSON string, or null. + + diff --git a/doc/snippets/Microsoft.Data.SqlTypes/SqlVector.xml b/doc/snippets/Microsoft.Data.SqlTypes/SqlVector.xml new file mode 100644 index 0000000000..7dea042145 --- /dev/null +++ b/doc/snippets/Microsoft.Data.SqlTypes/SqlVector.xml @@ -0,0 +1,44 @@ + + + + + Represents a vector value in SQL Server. + + + + + Constructs a vector with the given values. + + + + + + + + Represents a null instance without any attributes. + + + This property is provided for compatibility with DataTable. + In most cases, prefer using IsNull to check if a SqlVector instance is a null vector. + This is equivalent to null value. + + + + + Returns the number of elements in the vector. + + + + Returns the vector values as a memory region. No copies are made. + + + + + Constructs a null vector of the given length. SQL Server requires vector arguments to specify their length even when null. + + + Vector length must be non-negative. + + + + diff --git a/doc/snippets/Microsoft.Data/SqlDbTypeExtensions.xml b/doc/snippets/Microsoft.Data/SqlDbTypeExtensions.xml index a3d03229f5..cf2fda1536 100644 --- a/doc/snippets/Microsoft.Data/SqlDbTypeExtensions.xml +++ b/doc/snippets/Microsoft.Data/SqlDbTypeExtensions.xml @@ -5,11 +5,9 @@ Extensions for SqlDbType. - class provides enums which will be added. These are meant to be used by applications which cannot leverage the enums in available in newer .NET runtime versions. - ]]> @@ -20,5 +18,13 @@ The class provides enum value for JSON datatype. + + + Gets the enum value for the vector datatype. + + + enum value for vector datatype. + + diff --git a/doc/snippets/Microsoft.SqlServer.Server/Format.xml b/doc/snippets/Microsoft.SqlServer.Server/Format.xml index cf461f1129..b0a67131ba 100644 --- a/doc/snippets/Microsoft.SqlServer.Server/Format.xml +++ b/doc/snippets/Microsoft.SqlServer.Server/Format.xml @@ -12,41 +12,41 @@ The requirements for the format are: - + The with a property value of must be applied to the aggregate or UDT if it is defined in a class and not a structure. This controls the physical layout of the data fields and is used to force the members to be laid out sequentially in the order they appear. SQL Server uses this attribute to determine the field order for UDTs with multiple fields. - - + + The type must contain at least one member (serialized values cannot be zero bytes in size). - - + + All the fields of the aggregate must be blittable; that is, they must have a common representation in both managed and unmanaged memory and not require special handling by the interop marshaller. - - + + All the fields of the UDT should be of one of the following types that can be serialized: bool, byte, sbyte, short, ushort, int, uint, long, ulong, float, double, , , , , , , , , or other value types defined by the user that contain fields of one of these types. - - + + The aggregate must not specify a value for MaxByteSize - - + + The aggregate must not have any [NonSerialized] fields. - - + + Fields must not be marked as an explicit layout (with a of ). - + The requirements for the format are: - + The aggregate must specify a value for MaxByteSize - - + + Specify the attribute property. The default value is . - - + + If you omit any field in the or methods, the state of that field is not serialized. - + diff --git a/doc/snippets/Microsoft.SqlServer.Server/InvalidUdtException.xml b/doc/snippets/Microsoft.SqlServer.Server/InvalidUdtException.xml index 42d9b9885a..eb94f7f5fe 100644 --- a/doc/snippets/Microsoft.SqlServer.Server/InvalidUdtException.xml +++ b/doc/snippets/Microsoft.SqlServer.Server/InvalidUdtException.xml @@ -36,9 +36,10 @@ This method Looks up a localized string similar to '{0}' is an invalid user defined type, reason: {1}., and fills it in order with the corresponding and values. - - This function is exposed for backward compatibility and should be used with the default value for the parameter. - + +> [!IMPORTANT] +> This function is exposed for backward compatibility and should be used with the default value for the `resourceReason` parameter. + diff --git a/doc/snippets/Microsoft.SqlServer.Server/SqlUserDefinedTypeAttribute.xml b/doc/snippets/Microsoft.SqlServer.Server/SqlUserDefinedTypeAttribute.xml index d383c39842..be808f7878 100644 --- a/doc/snippets/Microsoft.SqlServer.Server/SqlUserDefinedTypeAttribute.xml +++ b/doc/snippets/Microsoft.SqlServer.Server/SqlUserDefinedTypeAttribute.xml @@ -87,22 +87,22 @@ The following features are supported when is set to : - The ability to create indexes on columns of this type. - The ability to create primary and foreign keys as well as CHECK and UNIQUE constraints on columns of this type. - The ability to use Transact-SQL ORDER BY, GROUP BY, and PARTITION BY clauses. In these cases, the binary representation of the type is used to determine the order. - The ability to use comparison operators in Transact-SQL statements. - The ability to persist computed columns of this type. + The ability to create indexes on columns of this type. + The ability to create primary and foreign keys as well as CHECK and UNIQUE constraints on columns of this type. + The ability to use Transact-SQL ORDER BY, GROUP BY, and PARTITION BY clauses. In these cases, the binary representation of the type is used to determine the order. + The ability to use comparison operators in Transact-SQL statements. + The ability to persist computed columns of this type. Note that both the Native and UserDefined serialization formats support the following comparison operators when is set to : - Equal to (=) - Not equal to (!=) - Greater than (>) - Less than (<) - Greater than or equal to (>=) - Less than or equal to (<=) + Equal to (=) + Not equal to (!=) + Greater than (>) + Less than (<) + Greater than or equal to (>=) + Less than or equal to (<=) diff --git a/eng/pipelines/akv-official-pipeline.yml b/eng/pipelines/akv-official-pipeline.yml new file mode 100644 index 0000000000..d7bc900bb8 --- /dev/null +++ b/eng/pipelines/akv-official-pipeline.yml @@ -0,0 +1,147 @@ +################################################################################# +# 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. # +################################################################################# + +name: $(Year:YY)$(DayOfYear)$(Rev:.r) + +# @TODO: Add triggers and schedules + +parameters: + - name: oneBranchType + displayName: 'OneBranch template' + type: 'string' + values: + - 'Official' + - 'NonOfficial' + default: 'Official' + + - name: buildConfiguration + displayName: 'Build configuration' + type: 'string' + values: + - 'Release' + - 'Debug' + default: 'Release' + + - name: publishSymbols + displayName: 'Publish symbols' + type: 'boolean' + default: false + + - name: runSdlTasks + displayName: 'Run SDL Tasks' + type: 'boolean' + default: true + +variables: + - template: /eng/pipelines/variables/common-variables.yml@self + - template: /eng/pipelines/variables/onebranch-variables.yml@self + - template: /eng/pipelines/variables/esrp-signing-variables.yml@self + - template: /eng/pipelines/variables/akv-official-variables.yml@self + +resources: + repositories: + - repository: templates + type: 'git' + name: 'OneBranch.Pipelines/GovernedTemplates' + ref: 'refs/heads/main' + +extends: + template: 'v2/OneBranch.${{ parameters.oneBranchType }}.CrossPlat.yml@templates' + + parameters: + featureFlags: + WindowsHostVersion: + Version: '2022' + + globalSdl: + # See https://aka.ms/obpipelines/sdl + + apiscan: + enabled: ${{ parameters.runSdlTasks }} + softwareFolder: '${{ variables.apiScanDllPath }}' + softwareName: 'Microsoft.Data.SqlClient' # Note: This name is registered with ApiScan + softwareVersionNum: '${{ variables.assemblyFileVersion }}' + symbolsFolder: '${{ variables.apiScanPdbPath }}' + + armory: + enabled: ${{ parameters.runSdlTasks }} + break: true + + asyncSdl: + # If this should be enabled, move supported tools under this item, + # see https://aka.ms/obpipelines/asyncsdl + enabled: false + + binskim: + enabled: ${{ parameters.runSdlTasks }} + break: true + + codeinspector: + enabled: ${{ parameters.runSdlTasks }} + logLevel: Error + + codeql: + enabled: ${{ parameters.runSdlTasks }} + sourceRoot: '$(REPO_ROOT)/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider' + # Note, this can only be done if project doesn't depend on other projects. In + # package reference mode, this is true, but if we ever enable project reference + # builds, this will have to be removed. + + credscan: + enabled: ${{ parameters.runSdlTasks }} + suppressionsFile: '$(REPO_ROOT)/.config/CredScanSuppressions.json' + + eslint: + enabled: false + + policheck: + enabled: ${{ parameters.runSdlTasks }} + break: true + exclusionFile: '$(REPO_ROOT)/.config/PolicheckExclusions.xml' + + roslyn: + enabled: ${{ parameters.runSdlTasks }} + break: true + # Requires RoslynAnalyzers task to be added after build task + + publishLogs: + enabled: ${{ parameters.runSdlTasks }} + + sbom: + enabled: ${{ parameters.runSdlTasks }} + packageName: 'Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider' + packageVersion: ${{ variables.nugetPackageVersion }} + + tsa: + # OneBranch publishes all sdl results to TSA. If TSA is disabled all SDL tools will + # be forced into 'break' build mode. + enabled: ${{ eq(parameters.oneBranchType, 'Official') }} + configFile: '$(REPO_ROOT)/.config/tsaoptions.json' + + stages: + - stage: BuildAkv + displayName: 'Build AKV' + jobs: + - template: /eng/pipelines/jobs/build-akv-official-job.yml@self + parameters: + apiScanDllPath: '${{ variables.apiScanDllPath }}' + apiScanPdbPath: '${{ variables.apiScanPdbPath }}' + assemblyFileVersion: '${{ variables.assemblyFileVersion }}' + buildConfiguration: '${{ parameters.buildConfiguration }}' + nugetPackageVersion: '${{ variables.nugetPackageVersion }}' + mdsPackageVersion: '${{ variables.mdsPackageVersion }}' + publishSymbols: '${{ parameters.publishSymbols }}' + signingAppRegistrationClientId: '$(SigningAppRegistrationClientId)' + signingAppRegistrationTenantId: '$(SigningAppRegistrationTenantId)' + signingAuthAkvName: '$(SigningAuthAkvName)' + signingAuthSignCertName: '$(SigningAuthSignCertName)' + signingEsrpClientId: '$(SigningEsrpClientId)' + signingEsrpConnectedServiceName: '$(SigningEsrpConnectedServiceName)' + symbolsAzureSubscription: '$(SymbolsAzureSubscription)' + symbolsPublishProjectName: '$(SymbolsPublishProjectName)' + symbolsPublishServer: '$(SymbolsPublishServer)' + symbolsPublishTokenUri: '$(SymbolsPublishTokenUri)' + symbolsUploadAccount: '$(SymbolsUploadAccount)' diff --git a/eng/pipelines/common/templates/jobs/build-signed-akv-package-job.yml b/eng/pipelines/common/templates/jobs/build-signed-akv-package-job.yml deleted file mode 100644 index d0ad120b10..0000000000 --- a/eng/pipelines/common/templates/jobs/build-signed-akv-package-job.yml +++ /dev/null @@ -1,70 +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. # -################################################################################# -parameters: - - name: symbolsFolder - type: string - default: symbols - - - name: softwareFolder - type: string - default: software - - - name: publishSymbols - type: boolean - -jobs: -- job: build_signed_akv_package - displayName: 'Build Signed AKV Provider Package' - pool: - type: windows # read more about custom job pool types at https://aka.ms/obpipelines/yaml/jobs - - variables: - - template: ../../../libraries/variables.yml@self - - steps: - - script: SET - displayName: 'Print Environment Variables' - - - template: ../steps/build-all-configurations-signed-dlls-step.yml@self - parameters: - product: AKV - packageRefMdsVersion: $(MDS_PackageRef_Version) - AssemblyFileVersion: $(AKVAssemblyFileVersion) - - - template: ../steps/code-analyze-step.yml@self - parameters: - analyzeType: all - product: AKV - packageRefMdsVersion: $(MDS_PackageRef_Version) - - - template: ../steps/esrp-code-signing-step.yml@self - parameters: - artifactType: dll - - - template: ../steps/generate-nuget-package-step.yml@self - parameters: - OutputDirectory: $(artifactDirectory) - nuspecPath: ${{variables.akvNuspecPath }} - NugetPackageVersion: ${{variables.AKVNuGetPackageVersion }} - referenceType: package - - - template: ../steps/esrp-code-signing-step.yml@self - parameters: - artifactType: pkg - - - template: ../steps/copy-dlls-for-test-step.yml@self - parameters: - product: AKV - referenceType: package - - # Publish symbols to servers - - template: ../steps/publish-symbols-step.yml@self - parameters: - referenceType: package - symbolsVersion: ${{variables.AKVNuGetPackageVersion }} - product: AKV - publishSymbols: ${{ parameters['PublishSymbols'] }} - symbolsArtifactName: akv_symbols_$(System.TeamProject)_$(Build.Repository.Name)_$(Build.SourceBranchName)_$(NuGetPackageVersion)_$(System.TimelineId) diff --git a/eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml b/eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml index 833f2da39c..186d8cfdee 100644 --- a/eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml @@ -44,10 +44,10 @@ jobs: - task: NuGetAuthenticate@1 displayName: 'NuGet Authenticate' - - task: UseDotNet@2 - displayName: 'Use .NET SDK 8.0.x' - inputs: - version: 8.0.x + - template: ../steps/ensure-dotnet-version.yml@self + parameters: + packageType: 'sdk' + version: '8.0' - ${{ parameters.downloadArtifactsSteps }} 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 66a2614c3e..9ad2c86e6a 100644 --- a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml +++ b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml @@ -92,6 +92,22 @@ jobs: - ${{ if ne(parameters.prebuildSteps, '') }}: - ${{ parameters.prebuildSteps }} # extra steps to run before the build like downloading sni and the required configuration + - powershell: | + $guid = [guid]::NewGuid().ToString() + Write-Host "##vso[task.setvariable variable=password;issecret=true]$guid" + displayName: 'Generate Password' + + - powershell: | + Write-Host "Password: $(password)" # prints *** to logs when set + + $variableValue = "$(password)" + if (-not [string]::IsNullOrEmpty($variableValue)) { + Write-Output "The password exists with a value." + } else { + Write-Output "The password does not exist or is empty." + } + displayName: 'Verify Password' + - ${{ if ne(parameters.configProperties, '{}') }}: - template: ../steps/update-config-file-step.yml@self # update config.json file parameters: @@ -178,8 +194,6 @@ jobs: user: ${{ parameters.configProperties.user }} ${{ if parameters.configProperties.saUser }}: saUser: ${{ parameters.configProperties.saUser }} - ${{ if parameters.configProperties.password }}: - password: ${{ parameters.configProperties.password }} ${{ if parameters.configProperties.SQLRootPath }}: SQLRootPath: ${{ parameters.configProperties.SQLRootPath }} ${{ if parameters.configProperties.x64AliasRegistryPath }}: @@ -217,26 +231,41 @@ jobs: referenceType: ${{ parameters.buildType }} testSet: ${{ parameters.testSet }} operatingSystem: ${{ parameters.operatingSystem }} - - - ${{ if and(eq(parameters.enableX86Test, true), eq(parameters.operatingSystem, 'Windows')) }}: # run x86 tests - - powershell: | - dotnet --info - - Invoke-WebRequest https://dot.net/v1/dotnet-install.ps1 -OutFile dotnet-install.ps1 - - # install .net x86 - $version = "LTS" - if (!"${{parameters.targetFramework }}".StartsWith("net4")) - { - $version = "${{parameters.targetFramework }}".Substring(3, "${{parameters.targetFramework }}".Length-3) - } - - .\dotnet-install.ps1 -Channel $version -Architecture x86 -InstallDir "$(dotnetx86RootPath)" - - $(dotnetx86RootPath)dotnet.exe --info - displayName: 'Install .NET x86 ' - condition: ne(variables['dotnetx86RootPath'], '') - + + - ${{ if and(eq(parameters.enableX86Test, true), eq(parameters.operatingSystem, 'Windows')) }}: + # Set up for x86 tests by manually installing dotnet for x86 to an alternative location. This + # is only used to execute the test runtime (framework to test is specified in build params), so + # it should be acceptable to just install a specific version in all cases. + # @TODO: This setup is very confusing. Ideally we should just be utilizing the dotnet installation + # earlier in the job. There has to be a cleaner way of doing this. + # As it stands now, we install dotnet LTS if we're running netfx tests, and the appropriate netX + # version if we're running netcore tests. Technically LTS is not supported by the UseDotNet task + # but we know we're using install-dotnet since we're on windows (for now ... this will not work + # forever and needs a serious rethinking). + - ${{ if ne(variables['dotnetx86RootPath'], '') }}: + - ${{ if startswith(parameters.targetFramework, 'net4')}}: + - template: ../steps/ensure-dotnet-version.yml + parameters: + installDir: "$(dotnetx86RootPath)" + packageType: "sdk" + usePreview: "false" + version: "LTS" + windowsArchitecture: "x86" + - ${{ else }}: + - script: | + set FrameworkVersion=${{ parameters.targetFramework }} + echo %FrameworkVersion% + set TrimmedFrameworkVersion=%FrameworkVersion:~3% + echo %TrimmedFrameworkVersion% + echo ##vso[task.setvariable variable=netVersionX86]%TrimmedFrameworkVersion% + displayName: "Trim dotnet version" + - template: ../steps/ensure-dotnet-version.yml + parameters: + installDir: "$(dotnetx86RootPath)" + packageType: "sdk" + usePreview: "false" + version: $(netVersionX86) + - template: ../steps/run-all-tests-step.yml@self parameters: debug: ${{ parameters.debug }} 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 3560e6a564..009e6f2647 100644 --- a/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml +++ b/eng/pipelines/common/templates/jobs/validate-signed-package-job.yml @@ -283,8 +283,11 @@ jobs: displayName: 'Verify all dlls status are Valid' - powershell: | - # This will check for ProductVersion and FileVersion - # For NetFx we have a different FileVersion, but product versions are all the same some may have and extra numbering at the end. we only check for # first parts + # This will check for ProductVersion and FileVersion. + # + # 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. foreach ( $pVersion in Get-ChildItem *.dll -Path $(extractedNugetPath) -Recurse | ForEach-Object versioninfo ) { @@ -329,7 +332,7 @@ jobs: displayName: 'Verify "File Version" matches provided pipeline variable "ASSEMBLY_FILE_VERSION" for DLLs' - powershell: | - #Change TestMicrosoftDataSqlClientVersion + # Change TestMicrosoftDataSqlClientVersion [Xml] $versionprops = Get-Content -Path "tools/props/Versions.props" $versionpropspath = "tools\props\Versions.props" @@ -340,7 +343,7 @@ jobs: displayName: 'Modify TestMicrosoftDataSqlClientVersion' - powershell: | - #Change TestMicrosoftDataSqlClientVersion + # Check assembly versions. [Xml] $versionprops = Get-Content -Path "tools/props/Versions.props" $AssemblyFileVersion = $versionprops.Project.PropertyGroup[0].AssemblyFileVersion 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 6065ce155e..e6a6c0443a 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 @@ -20,7 +20,6 @@ parameters: default: MDS values: - MDS - - AKV - MSS steps: @@ -30,6 +29,18 @@ steps: secureFile: netfxKeypair.snk retryCount: 5 +- task: UseDotNet@2 + displayName: 'Use .NET 9.x sdk' + inputs: + packageType: sdk + version: '9.x' + +- task: UseDotNet@2 + displayName: 'Install .NET 8.x runtime' + inputs: + packageType: runtime + version: '8.x' + - ${{ if eq(parameters.product, 'MDS') }}: - task: MSBuild@1 displayName: 'BuildAllConfigurations using build.proj' @@ -37,18 +48,3 @@ steps: solution: '**/build.proj' configuration: '${{parameters.Configuration }}' msbuildArguments: '-p:AssemblyFileVersion=${{parameters.AssemblyFileVersion }} -t:BuildAllConfigurations -p:GenerateNuget=false -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk' - -- ${{ if eq(parameters.product, 'AKV') }}: - - task: MSBuild@1 - displayName: 'BuildAKVNetFx using build.proj' - inputs: - solution: '**/build.proj' - configuration: '${{parameters.Configuration }}' - msbuildArguments: '-p:AssemblyFileVersion=${{parameters.AssemblyFileVersion }} -t:BuildAKVNetFx -p:NugetPackageVersion=${{parameters.packageRefMdsVersion }} -p:ReferenceType=Package -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk' - - - task: MSBuild@1 - displayName: 'BuildAKVNetCoreAllOS using build.proj' - inputs: - solution: '**/build.proj' - configuration: '${{parameters.Configuration }}' - msbuildArguments: '-p:AssemblyFileVersion=${{parameters.AssemblyFileVersion }} -t:BuildAKVNetCoreAllOS -p:NugetPackageVersion=${{parameters.packageRefMdsVersion }} -p:ReferenceType=Package -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk' 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 f747fa57e6..c70fe776a7 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 @@ -55,7 +55,7 @@ steps: inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetCore -p:ReferenceType=${{parameters.referenceType }} ' + msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }}' - task: MSBuild@1 displayName: 'MSBuild Build Tests for ${{parameters.TargetNetCoreVersion }}' @@ -64,11 +64,13 @@ steps: msbuildArchitecture: x64 msbuildArguments: '-t:BuildTestsNetCore -p:ReferenceType=${{parameters.referenceType }} -p:TargetNetCoreVersion=${{parameters.TargetNetCoreVersion }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }} -p:Configuration=${{parameters.configuration }}' +# Don't run unit tests using package reference. Unit tests are only run using project reference. + - task: DotNetCoreCLI@2 displayName: 'Run Functional Tests for ${{parameters.TargetNetCoreVersion }}' inputs: command: test - projects: 'src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.Tests.csproj' + 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"' - task: DotNetCoreCLI@2 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 ab77af3ee9..5d9f194c48 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 @@ -55,19 +55,21 @@ steps: inputs: solution: build.proj msbuildArchitecture: x64 - msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetFx -p:ReferenceType=${{parameters.referenceType }} ' + msbuildArguments: '-p:Configuration=${{parameters.configuration }} -t:BuildAKVNetFx -p:ReferenceType=${{parameters.referenceType }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.NugetPackageVersion }}' - 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 }}' + +# Don't run unit tests using package reference. Unit tests are only run using project reference. - task: DotNetCoreCLI@2 displayName: 'Run Functional Tests for ${{parameters.TargetNetFxVersion }}' inputs: command: test - projects: 'src\Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.Tests.csproj' + 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"' - task: DotNetCoreCLI@2 diff --git a/eng/pipelines/common/templates/steps/ci-prebuild-step.yml b/eng/pipelines/common/templates/steps/ci-prebuild-step.yml index b8771702dd..efe17856d8 100644 --- a/eng/pipelines/common/templates/steps/ci-prebuild-step.yml +++ b/eng/pipelines/common/templates/steps/ci-prebuild-step.yml @@ -20,17 +20,17 @@ parameters: - Package steps: -- task: UseDotNet@2 - displayName: 'Use .NET 9.x sdk' - inputs: +- template: ensure-dotnet-version.yml + parameters: packageType: sdk - version: '9.x' + usePreview: false + version: 9.0 -- task: UseDotNet@2 - displayName: 'Install .NET 8.x runtime' - inputs: +- template: ensure-dotnet-version.yml + parameters: packageType: runtime - version: '8.x' + usePreview: false + version: 8.0 - ${{if eq(parameters.debug, true)}}: - powershell: | @@ -53,3 +53,5 @@ steps: - ${{ 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 9f0d7ad863..e938c909fd 100644 --- a/eng/pipelines/common/templates/steps/ci-project-build-step.yml +++ b/eng/pipelines/common/templates/steps/ci-project-build-step.yml @@ -32,22 +32,21 @@ parameters: - MDS - AKV - all + - allNoDocs steps: -- task: UseDotNet@2 - displayName: 'Use .NET 9.x sdk' - inputs: - packageType: sdk - version: '9.x' +- template: ./ensure-dotnet-version.yml@self + parameters: + packageType: 'sdk' + version: '9.0' -- task: UseDotNet@2 - displayName: 'Install .NET 8.x runtime' - inputs: - packageType: runtime - version: '8.x' +- template: ./ensure-dotnet-version.yml@self + parameters: + packageType: 'runtime' + version: '8.0' - ${{ if or(eq(parameters.operatingSystem, 'Windows'), eq(parameters.operatingSystem, 'deferedToRuntime')) }}: - - ${{ if or(eq(parameters.build, 'MDS'), eq(parameters.build, 'all')) }}: + - ${{ if or(eq(parameters.build, 'MDS'), eq(parameters.build, 'all'), eq(parameters.build, 'allNoDocs')) }}: - task: MSBuild@1 displayName: 'Restore nugets [Win]' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) @@ -57,18 +56,31 @@ steps: msbuildArguments: '-t:restore' retryCountOnTaskFailure: 1 - - task: MSBuild@1 - displayName: 'Build Driver [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:GenerateNuGet=false -p:BuildNumber=${{ parameters.buildNumber }}' - clean: true + - ${{ if eq(parameters.build, 'allNoDocs') }}: + - task: MSBuild@1 + displayName: 'Build Driver [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 }}' + clean: true + + - ${{ if or(eq(parameters.build, 'MDS'), eq(parameters.build, 'all')) }}: + - task: MSBuild@1 + displayName: 'Build Driver [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:GenerateNuGet=false -p:BuildNumber=${{ parameters.buildNumber }}' + clean: true - - ${{ if or(eq(parameters.build, 'AKV'), eq(parameters.build, 'all')) }}: + - ${{ if or(eq(parameters.build, 'AKV'), eq(parameters.build, 'all'), eq(parameters.build, 'allNoDocs')) }}: - task: MSBuild@1 displayName: 'Build AKV Provider NetFx [Win]' condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) diff --git a/eng/pipelines/common/templates/steps/code-analyze-step.yml b/eng/pipelines/common/templates/steps/code-analyze-step.yml index 1ba79cda3b..9807541a70 100644 --- a/eng/pipelines/common/templates/steps/code-analyze-step.yml +++ b/eng/pipelines/common/templates/steps/code-analyze-step.yml @@ -22,7 +22,6 @@ parameters: default: MDS values: - MDS - - AKV - MSS steps: @@ -35,14 +34,6 @@ steps: msBuildArchitecture: x64 setupCommandlinePicker: vs2022 msBuildCommandline: 'msbuild ${{parameters.sourceRoot}}\build.proj -p:configuration=Release -p:GenerateNuget=false -p:BuildTools=false -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk' - - ${{ if eq(parameters.product, 'AKV') }}: - - task: securedevelopmentteam.vss-secure-development-tools.build-task-roslynanalyzers.RoslynAnalyzers@3 - displayName: 'Guardian Dotnet Analyzers ' - inputs: - msBuildVersion: 17.0 - msBuildArchitecture: x64 - setupCommandlinePicker: vs2022 - msBuildCommandline: 'msbuild ${{parameters.sourceRoot}}\build.proj -p:configuration=Release -p:GenerateNuget=false -p:BuildTools=false -p:NugetPackageVersion=${{parameters.packageRefMdsVersion }} -p:ReferenceType=Package -t:BuildAKVNetCoreAllOS -p:SigningKeyPath=$(Agent.TempDirectory)\netfxKeypair.snk' - ${{ if or(eq(parameters.analyzeType, 'inspect'), eq(parameters.analyzeType, 'all')) }}: - task: securedevelopmentteam.vss-secure-development-tools.build-task-codeinspector.CodeInspector@2 diff --git a/eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml b/eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml index 2d224d156c..5cff58cd4e 100644 --- a/eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml +++ b/eng/pipelines/common/templates/steps/configure-sql-server-linux-step.yml @@ -7,7 +7,7 @@ parameters: - name: password type: string default: $(password) - + - name: condition type: string default: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) @@ -18,11 +18,9 @@ steps: sudo systemctl stop mssql-server # Password for the SA user (required) + MSSQL_SA_PW=${{parameters.password }} - if [ "$MSSQL_SA_PW" = "generated_placeholder" ]; then - exit 0 - fi - + # Product ID of the version of SQL server you're installing # Must be evaluation, developer, express, web, standard, enterprise, or your 25 digit product key MSSQL_PID='enterprise' diff --git a/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml b/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml new file mode 100644 index 0000000000..3e83d6b830 --- /dev/null +++ b/eng/pipelines/common/templates/steps/configure-sql-server-macos-step.yml @@ -0,0 +1,87 @@ +################################################################################# +# 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. # +################################################################################# +parameters: + - name: password + type: string + default: $(password) + + - name: condition + type: string + default: and(succeeded(), eq(variables['Agent.OS'], 'Darwin')) + +steps: +# Linux only steps +- bash: | + # The "user" pipeline variable conflicts with homebrew, causing errors during install. Set it back to the pipeline user. + USER=`whoami` + SQLCMD_ERRORS=$(Agent.TempDirectory)/sqlcmd_err.log + echo $SQLCMD_ERRORS + + brew install colima + brew install --cask docker + brew tap microsoft/mssql-release https://github.com/Microsoft/homebrew-mssql-release + brew update + HOMEBREW_ACCEPT_EULA=Y brew install mssql-tools18 + colima start --arch x86_64 + docker --version + docker pull mcr.microsoft.com/mssql/server:2022-latest + + # Password for the SA user (required) + MSSQL_SA_PW=${{parameters.password }} + + docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=$MSSQL_SA_PW" -p 1433:1433 -p 1434:1434 --name sql1 --hostname sql1 -d mcr.microsoft.com/mssql/server:2022-latest + + sleep 5 + + docker ps -a + + # Connect to server and get the version: + counter=1 + errstatus=1 + while [ $counter -le 20 ] && [ $errstatus = 1 ] + do + echo Waiting for SQL Server to start... + sleep 3 + sqlcmd -S 0.0.0.0 -No -U sa -P $MSSQL_SA_PW -Q "SELECT @@VERSION" 2>$SQLCMD_ERRORS + errstatus=$? + ((counter++)) + done + + # Display error if connection failed: + if [ $errstatus = 1 ] + then + echo Cannot connect to SQL Server, installation aborted + cat $SQLCMD_ERRORS + rm -f $SQLCMD_ERRORS + exit $errstatus + else + rm -f $SQLCMD_ERRORS + fi + + echo "Use sqlcmd to show which IP addresses are being listened on..." + echo 0.0.0.0 + sqlcmd -S 0.0.0.0 -No -U sa -P $MSSQL_SA_PW -Q "SELECT @@VERSION" -l 2 + echo 127.0.0.1 + sqlcmd -S 127.0.0.1 -No -U sa -P $MSSQL_SA_PW -Q "SELECT @@VERSION" -l 2 + echo ::1 + sqlcmd -S ::1 -No -U sa -P $MSSQL_SA_PW -Q "SELECT @@VERSION" -l 2 + echo localhost + sqlcmd -S localhost -No -U sa -P $MSSQL_SA_PW -Q "SELECT @@VERSION" -l 2 + echo "(sqlcmd default / not specified)" + sqlcmd -No -U sa -P $MSSQL_SA_PW -Q "SELECT @@VERSION" -l 2 + + echo "Configuring Dedicated Administer Connections to allow remote connections..." + sqlcmd -S 0.0.0.0 -No -U sa -P $MSSQL_SA_PW -Q "sp_configure 'remote admin connections', 1; RECONFIGURE;" + if [ $? = 1 ] + then + echo "Error configuring DAC for remote access." + exit $errstatus + else + echo "Configuration complete." + fi + + displayName: 'Configure SQL Server [macOS]' + condition: ${{parameters.condition }} diff --git a/eng/pipelines/common/templates/steps/configure-sql-server-step.yml b/eng/pipelines/common/templates/steps/configure-sql-server-step.yml index b8c2856522..bfb89e34b9 100644 --- a/eng/pipelines/common/templates/steps/configure-sql-server-step.yml +++ b/eng/pipelines/common/templates/steps/configure-sql-server-step.yml @@ -93,6 +93,12 @@ steps: parameters: password: ${{parameters.password}} +- ${{ elseif eq(parameters.operatingSystem, 'Mac') }}: + # macOS only steps + - template: configure-sql-server-macos-step.yml@self + parameters: + password: ${{parameters.password}} + # Common steps - task: DotNetCoreCLI@2 displayName: 'Build Ext Utilities' diff --git a/eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml b/eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml index f0c27e6152..d159191b01 100644 --- a/eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml +++ b/eng/pipelines/common/templates/steps/configure-sql-server-win-step.yml @@ -101,11 +101,7 @@ steps: - powershell: | $password = "${{parameters.password }}" - if ( "generated_placeholder" -eq $password ) - { - $password = [guid]::NewGuid().ToString() - } - + $machineName = $env:COMPUTERNAME if ("${{parameters.instanceName }}" -ne "MSSQLSERVER"){ @@ -195,6 +191,44 @@ steps: displayName: 'Setup SQL Alias [Win]' condition: ${{parameters.condition }} +- powershell: | + # Create Certificate + $computerDnsName = [System.Net.Dns]::Resolve($null).HostName + $certificate = New-SelfSignedCertificate -DnsName $computerDnsName,localhost -CertStoreLocation cert:\LocalMachine\My -FriendlyName test99 -KeySpec KeyExchange + + # Get path to Private key (used later) + $keyPath = $certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName + $machineKeyPath = "$env:ProgramData\Microsoft\Crypto\RSA\MachineKeys\$keyPath" + + # Add certificate to trusted roots + $store = new-object System.Security.Cryptography.X509Certificates.X509Store( + [System.Security.Cryptography.X509Certificates.StoreName]::Root, + "localmachine" + ) + + $store.open("MaxAllowed") + $store.add($certificate) + $store.close() + + # Get SQL Server instances and add the Certificate + $instances = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL' + foreach ($instance in $instances){ + $instance | ForEach-Object { + $_.PSObject.Properties | Where-Object { $_.Name -notmatch '^PS.*' } | ForEach-Object { + Set-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$($_.Value)\MSSQLServer\SuperSocketNetLib" -Name Certificate -Value $certificate.Thumbprint.ToLower() + + # Grant read access to Private Key for SQL Service Account + if ($($_.Name) -eq "MSSQLSERVER") { + icacls $machineKeyPath /grant "NT Service\MSSQLSERVER:R" + } else { + icacls $machineKeyPath /grant "NT Service\MSSQL`$$($_.Name):R" + } + } + } + } + displayName: 'Add SQL Certificate [Win]' + condition: ${{parameters.condition }} + - powershell: | # You need to restart SQL Server for the change to persist # -Force takes care of any dependent services, like SQL Agent. @@ -210,6 +244,7 @@ steps: } Restart-Service -Name "$serviceName" -Force + Restart-Service -Name MSSQLSERVER* -Force displayName: 'Restart SQL Server [Win]' condition: ${{parameters.condition }} 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 30d232b8d0..098286866c 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 @@ -33,7 +33,6 @@ parameters: default: MDS values: - MDS - - AKV - MSS steps: @@ -78,24 +77,7 @@ steps: Write-Host "Artifacts fetched for testing" Get-Location - displayName: 'Prepare ${{ targetFramework }} Arifacts for Testing' - - - ${{ if eq(parameters.product, 'AKV') }}: - - powershell: | - $software = '${{parameters.softwareFolder}}' - $tf = '${{ targetFramework }}' - md $software\win\$tf - - Copy-Item "artifacts\${{parameters.referenceType }}\bin\Windows_NT\${{parameters.Configuration }}.AnyCPU\AzureKeyVaultProvider\$tf\Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.dll" "$software\win\$tf" -recurse - - $symbols = '${{parameters.symbolsFolder}}' - md $symbols\win\$tf - - Copy-Item "artifacts\${{parameters.referenceType }}\bin\Windows_NT\${{parameters.Configuration }}.AnyCPU\AzureKeyVaultProvider\$tf\Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.pdb" "$symbols\win\$tf" -recurse - - Write-Host "Artifacts fetched for testing" - Get-Location - displayName: 'Prepare ${{ targetFramework }} Arifacts for Testing' + displayName: 'Prepare ${{ targetFramework }} Artifacts for Testing' - powershell: | $software = '${{parameters.softwareFolder}}' diff --git a/eng/pipelines/common/templates/steps/ensure-dotnet-version.yml b/eng/pipelines/common/templates/steps/ensure-dotnet-version.yml new file mode 100644 index 0000000000..fd6ba9674b --- /dev/null +++ b/eng/pipelines/common/templates/steps/ensure-dotnet-version.yml @@ -0,0 +1,108 @@ +################################################################################# +# 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. # +################################################################################# + +# Installs dotnet sdk/runtime using UseDotNet on non-windows agents, but uses a custom ps script +# for installation on Windows. +# +# Reason for not using UseDotNet task: +# [BUG]: UseDotNet task installs x86 build on Windows arm64 +# https://github.com/microsoft/azure-pipelines-tasks/issues/20300 + +parameters: + - # Directory where dotnet binaries should be installed. If not specified, defaults to the + # agent's tools directory. + name: installDir + type: string + default: '' + + - # Whether to install the full SDK or the "dotnet" runtime + name: packageType + type: string + values: + - runtime + - sdk + + - # Whether to enable preview versions of dotnet + name: usePreview + type: boolean + default: false + + - # Version to install - supports full major.minor.patch, but can be shortened to major.minor + # to get the latest within a given channel. Do not append .x to the end (as per UseDotNet), + # it is not supported by dotnet-install script, and we will add it if we use UseDotNet task. + name: version + type: string + + - # Specifies the architecture to install, only applies on Windows agents. If not provided, + # defaults to the same architecture as the agent. + name: windowsArchitecture + type: string + default: '' + values: + - '' + - 'x86' + - 'x64' + - 'arm64' + +steps: + # Note: as much as I'd love to use template expressions here, I can't because ${{ }} is + # evaluated at compile time, while Agent is not known until runtime. + - powershell: |- + # Propagate parameters from pipeline ####################### + $agentToolsDir = '$(Agent.ToolsDirectory)' + echo "agentToolsDir= $agentToolsDir" + + $architecture = '${{ parameters.windowsArchitecture }}' + echo "architecture= $architecture" + + $installDir = '${{ parameters.installDir }}' + echo "installDir= $installDir" + + $packageType = '${{ parameters.packageType }}' + echo "packageType= $packageType" + + $usePreview = '${{ parameters.usePreview }}' + echo "usePreview= $usePreview" + + $version = '${{ parameters.version }}' + echo "version= $version" + + # Install dotnet ########################################### + Invoke-WebRequest -Uri "https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.ps1" -OutFile "dotnet-install.ps1" + + $installDir = If ($installDir) { $installDir } Else { $agentToolsDir } + $installParams = @{ + Architecture = $architecture + Channel = $version + InstallDir = $installDir + Quality = If ($usePreview) { "GA" } Else { "preview" } + } + + if ($packageType -eq 'runtime') { + $installParams.add('Runtime', 'dotnet') + } + + & ./dotnet-install.ps1 @installParams + Remove-Item dotnet-install.ps1 -ErrorAction:Ignore + + # Propagate values back out to pipeline #################### + Write-Host "##vso[task.setvariable variable=DOTNET_ROOT]${installDir}" + Write-Host "##vso[task.prependpath]${installDir}" + displayName: 'Install dotnet ${{ parameters.version }} ${{ parameters.packageType }} for Windows ${{ parameters.windowsArchitecture }}' + condition: eq(variables['Agent.OS'], 'Windows_NT') + + - task: UseDotNet@2 + displayName: 'Install dotnet ${{ parameters.version }} ${{ parameters.packageType }}' + inputs: + ${{ if ne(parameters.installDir, '') }}: + installationPath: '${{ parameters.installDir }}' + ${{ else }}: + installationPath: '$(Agent.ToolsDirectory)' + packageType: '${{ parameters.packageType }}' + version: '${{ parameters.version }}.x' + + includePreviewVersions: '${{ parameters.usePreview }}' + condition: ne(variables['Agent.OS'], 'Windows_NT') diff --git a/eng/pipelines/common/templates/steps/esrp-code-signing-step.yml b/eng/pipelines/common/templates/steps/esrp-code-signing-step.yml index a2851ed463..e9d62ad074 100644 --- a/eng/pipelines/common/templates/steps/esrp-code-signing-step.yml +++ b/eng/pipelines/common/templates/steps/esrp-code-signing-step.yml @@ -17,6 +17,10 @@ parameters: type: string default: $(artifactDirectory) + - name: ESRPConnectedServiceName + type: string + default: $(ESRPConnectedServiceName) + - name: appRegistrationClientId type: string default: $(appRegistrationClientId) @@ -25,16 +29,28 @@ parameters: type: string default: $(appRegistrationTenantId) + - name: AuthAKVName + type: string + default: $(AuthAKVName) + + - name: AuthSignCertName + type: string + default: $(AuthSignCertName) + + - name: EsrpClientId + type: string + default: $(EsrpClientId) + steps: - ${{ if eq(parameters.artifactType, 'dll') }}: - task: EsrpMalwareScanning@5 displayName: 'ESRP MalwareScanning' inputs: - ConnectedServiceName: 'ESRP Workload Identity federation service-ADO.Net' + ConnectedServiceName: '${{parameters.ESRPConnectedServiceName }}' AppRegistrationClientId: '${{parameters.appRegistrationClientId }}' AppRegistrationTenantId: '${{parameters.appRegistrationTenantId }}' - AuthAKVName: SqlClientDrivers - AuthCertName: 'ESRP-Release-Auth' + EsrpClientId: '${{parameters.EsrpClientId }}' + UseMSIAuthentication: true FolderPath: '${{parameters.sourceRoot }}' Pattern: '*.dll' CleanupTempStorage: 1 @@ -42,12 +58,13 @@ steps: - task: EsrpCodeSigning@5 displayName: 'ESRP CodeSigning' inputs: - ConnectedServiceName: 'ESRP Workload Identity federation service-ADO.Net' + ConnectedServiceName: '${{parameters.ESRPConnectedServiceName }}' AppRegistrationClientId: '${{parameters.appRegistrationClientId }}' AppRegistrationTenantId: '${{parameters.appRegistrationTenantId }}' - AuthAKVName: SqlClientDrivers - AuthCertName: 'ESRP-Release-Auth' - AuthSignCertName: 'ESRP-Release-Sign2' + EsrpClientId: '${{parameters.EsrpClientId }}' + UseMSIAuthentication: true + AuthAKVName: '${{parameters.AuthAKVName }}' + AuthSignCertName: '${{parameters.AuthSignCertName }}' FolderPath: '${{parameters.sourceRoot }}' Pattern: '*.dll' signConfigType: inlineSignParams @@ -94,11 +111,11 @@ steps: - task: EsrpMalwareScanning@5 displayName: 'ESRP MalwareScanning Nuget Package' inputs: - ConnectedServiceName: 'ESRP Workload Identity federation service-ADO.Net' + ConnectedServiceName: '${{parameters.ESRPConnectedServiceName }}' AppRegistrationClientId: '${{parameters.appRegistrationClientId }}' AppRegistrationTenantId: '${{parameters.appRegistrationTenantId }}' - AuthAKVName: SqlClientDrivers - AuthCertName: 'ESRP-Release-Auth' + EsrpClientId: '${{parameters.EsrpClientId }}' + UseMSIAuthentication: true FolderPath: '${{parameters.artifactDirectory }}' Pattern: '*.*nupkg' CleanupTempStorage: 1 @@ -107,12 +124,13 @@ steps: displayName: 'ESRP CodeSigning Nuget Package' inputs: inputs: - ConnectedServiceName: 'ESRP Workload Identity federation service-ADO.Net' + ConnectedServiceName: '${{parameters.ESRPConnectedServiceName }}' AppRegistrationClientId: '${{parameters.appRegistrationClientId }}' AppRegistrationTenantId: '${{parameters.appRegistrationTenantId }}' - AuthAKVName: SqlClientDrivers - AuthCertName: 'ESRP-Release-Auth' - AuthSignCertName: 'ESRP-Release-Sign2' + EsrpClientId: '${{parameters.EsrpClientId }}' + UseMSIAuthentication: true + AuthAKVName: '${{parameters.AuthAKVName }}' + AuthSignCertName: '${{parameters.AuthSignCertName }}' FolderPath: '${{parameters.artifactDirectory }}' Pattern: '*.*nupkg' signConfigType: inlineSignParams diff --git a/eng/pipelines/common/templates/steps/override-sni-version.yml b/eng/pipelines/common/templates/steps/override-sni-version.yml new file mode 100644 index 0000000000..e3754b40b7 --- /dev/null +++ b/eng/pipelines/common/templates/steps/override-sni-version.yml @@ -0,0 +1,81 @@ +################################################################################# +# 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. # +################################################################################# + +parameters: +- name: SNIVersion + type: string + +- name: SNIValidationFeed + type: string + +steps: +- task: PowerShell@2 + displayName: Add SNI Validation Feed in Nuget.config + inputs: + targetType: inline + script: | + Write-Host "SNI validation feed to use = ${{parameters.SNIValidationFeed}}" + + # define file to update + $NugetCfg = Join-Path -Path '.' -ChildPath 'NuGet.config' + type $NugetCfg + + # load content of xml from file defined above + $xml = New-Object XML + $xml.Load($NugetCfg) + + # define namespace used to read a node + $nsm = New-Object Xml.XmlNamespaceManager($xml.NameTable) + $nsm.AddNamespace('ns', $xml.DocumentElement.NamespaceURI) + + # get the package sources node + $packageSources = $xml.SelectSingleNode('//ns:packageSources', $nsm) + + # define new package source + $newSource = $xml.CreateElement("add") + $newSource.SetAttribute("key","SNIValidation") + $newSource.SetAttribute("value","${{parameters.SNIValidationFeed}}") + + # add the new package source + $packageSources.AppendChild($newSource) + + # save the xml file + $xml.Save($NugetCfg) + type $NugetCfg +- task: PowerShell@2 + displayName: Update SNI Version in Versions.props + inputs: + targetType: inline + script: | + Write-Host "SNI Version to test = ${{parameters.SNIVersion}}" + + # define file to update + $PropsPath = Join-Path -Path '.' -ChildPath 'tools\props\Versions.props' + type $PropsPath + + # load content of xml from file defined above + $xml = New-Object XML + $xml.Load($PropsPath) + + # define namespace used to read a node + $nsm = New-Object Xml.XmlNamespaceManager($xml.NameTable) + $nsm.AddNamespace('ns', $xml.DocumentElement.NamespaceURI) + + # update the node inner text for netfx + $netFxSniVersion = $xml.SelectSingleNode('//ns:MicrosoftDataSqlClientSniVersion', $nsm) + Write-Host "Current .NET Framework SNI Version = $($netFxSniVersion.InnerText)" + $netFxSniVersion.InnerText = "${{parameters.SNIVersion}}" + + # update the node inner text for net core + $netCoreSniVersion = $xml.SelectSingleNode('//ns:MicrosoftDataSqlClientSNIRuntimeVersion', $nsm) + Write-Host "Current .NET Core SNI Version = $($netCoreSniVersion.InnerText)" + $netCoreSniVersion.InnerText = "${{parameters.SNIVersion}}" + + # save the xml file + $xml.Save($PropsPath) + type $PropsPath +- task: NuGetAuthenticate@1 + displayName: 'NuGet Authenticate with SNI Validation Feed' diff --git a/eng/pipelines/common/templates/steps/pre-build-step.yml b/eng/pipelines/common/templates/steps/pre-build-step.yml index 327b5f21a5..f6506183b3 100644 --- a/eng/pipelines/common/templates/steps/pre-build-step.yml +++ b/eng/pipelines/common/templates/steps/pre-build-step.yml @@ -4,6 +4,16 @@ # See the LICENSE file in the project root for more information. # ################################################################################# steps: +- template: ./ensure-dotnet-version.yml@self + parameters: + packageType: 'sdk' + version: '9.0' + +- template: ./ensure-dotnet-version.yml@self + parameters: + packageType: 'runtime' + version: '8.0' + - script: SET displayName: 'Print Environment Variables' @@ -17,4 +27,4 @@ steps: displayName: 'Start SQLBrowser' - task: NuGetToolInstaller@1 - displayName: 'Use NuGet ' + displayName: 'Use NuGet' diff --git a/eng/pipelines/common/templates/steps/publish-symbols-step.yml b/eng/pipelines/common/templates/steps/publish-symbols-step.yml index cd1954ce77..52e622d9a3 100644 --- a/eng/pipelines/common/templates/steps/publish-symbols-step.yml +++ b/eng/pipelines/common/templates/steps/publish-symbols-step.yml @@ -45,7 +45,6 @@ parameters: default: MDS values: - MDS - - AKV - MSS steps: @@ -71,24 +70,6 @@ steps: Pat: $(System.AccessToken) condition: and(succeeded(), ${{ eq(parameters.publishSymbols, 'true') }}) -- ${{ if eq(parameters.product, 'AKV') }}: - - task: PublishSymbols@2 - displayName: 'Upload symbols to ${{parameters.SymAccount }} org' - inputs: - SymbolsFolder: '$(Build.SourcesDirectory)\artifacts\${{parameters.referenceType }}\bin' - SearchPattern: | - Windows_NT/$(Configuration).AnyCPU/AzureKeyVaultProvider/**/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.pdb - AnyOS/$(Configuration).AnyCPU/AzureKeyVaultProvider/**/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.pdb - IndexSources: false - SymbolServerType: TeamServices - SymbolsMaximumWaitTime: 60 - SymbolExpirationInDays: 1825 # 5 years - SymbolsProduct: Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider - SymbolsVersion: ${{parameters.symbolsVersion }} - SymbolsArtifactName: ${{parameters.symbolsArtifactName }} - Pat: $(System.AccessToken) - condition: and(succeeded(), ${{ eq(parameters.publishSymbols, 'true') }}) - - task: AzureCLI@2 displayName: 'Publish symbols' condition: and(succeeded(), ${{ eq(parameters.publishSymbols, 'true') }}) 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 f0eb2eff66..b9a870ada4 100644 --- a/eng/pipelines/common/templates/steps/run-all-tests-step.yml +++ b/eng/pipelines/common/templates/steps/run-all-tests-step.yml @@ -53,6 +53,21 @@ steps: condition: succeededOrFailed() - ${{if eq(parameters.operatingSystem, 'Windows')}}: + - ${{if eq(parameters.referenceType, 'Project')}}: + - task: MSBuild@1 + displayName: 'Run Unit Tests ${{parameters.msbuildArchitecture }}' + inputs: + solution: build.proj + msbuildArchitecture: ${{parameters.msbuildArchitecture }} + platform: '${{parameters.platform }}' + configuration: '${{parameters.configuration }}' + ${{ if eq(parameters.msbuildArchitecture, 'x64') }}: + msbuildArguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }}' + ${{ else }}: # x86 + msbuildArguments: '-t:RunUnitTests -p:TF=${{parameters.targetFramework }} -p:TestMicrosoftDataSqlClientVersion=${{parameters.nugetPackageVersion }} -p:DotnetPath=${{parameters.dotnetx86RootPath }}' + condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) + retryCountOnTaskFailure: 1 + - task: MSBuild@1 displayName: 'Run Functional Tests ${{parameters.msbuildArchitecture }}' inputs: @@ -82,6 +97,19 @@ steps: retryCountOnTaskFailure: 2 - ${{ else }}: # Linux or macOS + - ${{if eq(parameters.referenceType, 'Project')}}: + - task: DotNetCoreCLI@2 + displayName: 'Run Unit Tests' + inputs: + 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 }}' + verbosityRestore: Detailed + verbosityPack: Detailed + retryCountOnTaskFailure: 1 + condition: and(succeeded(), ne(variables['Agent.OS'], 'Windows_NT')) + - task: DotNetCoreCLI@2 displayName: 'Run Functional Tests' inputs: diff --git a/eng/pipelines/common/templates/steps/update-config-file-step.yml b/eng/pipelines/common/templates/steps/update-config-file-step.yml index 78a1cfebe3..f49e552323 100644 --- a/eng/pipelines/common/templates/steps/update-config-file-step.yml +++ b/eng/pipelines/common/templates/steps/update-config-file-step.yml @@ -126,7 +126,7 @@ parameters: steps: # All properties should be added here, and this template should be used for any manipulation of the config.json file. -- powershell: | +- pwsh: | $jdata = Get-Content -Raw "config.default.json" | ConvertFrom-Json foreach ($p in $jdata) { @@ -186,7 +186,7 @@ steps: displayName: 'Update config.json' - ${{ if eq(parameters.debug, true) }}: - - powershell: | + - pwsh: | $jdata = Get-Content -Raw "config.json" | ConvertFrom-Json foreach ($p in $jdata) { 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 6f5a528345..4eac341108 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 @@ -67,18 +67,12 @@ steps: $parent_xpath = '/Project/ItemGroup/PackageReference[@Include="Microsoft.Data.SqlClient"]' $node = $Doc.SelectSingleNode($parent_xpath) - if($node -ne $null){ - $node.Version="${{parameters.nugetPackageVersion }}" - } - else{ + if($node -eq $null){ $packagerefnode = $doc.createelement("packagereference") $value = $doc.selectsinglenode('/project/itemgroup/projectreference') $attrinclude = $doc.createattribute("include") $attrinclude.value = "microsoft.data.sqlclient" - $attrversion = $doc.createattribute("version") - $attrversion.value = "${{parameters.nugetPackageVersion }}" $packagerefnode.attributes.append($attrinclude) - $packagerefnode.attributes.append($attrversion) $parentNode.AppendChild($packageRefNode) } diff --git a/eng/pipelines/dotnet-sqlclient-ci-core.yml b/eng/pipelines/dotnet-sqlclient-ci-core.yml index 41a2a25416..4fa8c6bcbc 100644 --- a/eng/pipelines/dotnet-sqlclient-ci-core.yml +++ b/eng/pipelines/dotnet-sqlclient-ci-core.yml @@ -41,6 +41,18 @@ parameters: type: object default: [false, true] +- name: SNIVersion + displayName: | + SNI Version Override + type: string + default: '' + +- name: SNIValidationFeed + displayName: | + SNI Version Override Nuget Feed + type: string + default: https://sqlclientdrivers.pkgs.visualstudio.com/ADO.Net/_packaging/SNIValidation/nuget/v3/index.json + - name: codeCovTargetFrameworks displayName: 'Code Coverage Target Frameworks' type: object @@ -59,7 +71,7 @@ parameters: variables: - template: libraries/ci-build-variables.yml@self - + - name: artifactName value: Artifacts @@ -73,6 +85,12 @@ stages: - template: common/templates/jobs/ci-build-nugets-job.yml@self parameters: artifactName: $(artifactName) + ${{if ne(parameters.SNIVersion, '')}}: + prebuildSteps: + - template: common/templates/steps/override-sni-version.yml@self + parameters: + SNIVersion: ${{parameters.SNIVersion}} + SNIValidationFeed: ${{parameters.SNIValidationFeed}} - template: common/templates/stages/ci-run-tests-stage.yml@self parameters: @@ -81,12 +99,24 @@ stages: ${{ if eq(parameters.buildType, 'Package') }}: dependsOn: build_nugets - 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 ne(parameters.SNIVersion, '')}}: + prebuildSteps: # steps to run prior to building and running tests on each job + - 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 postTestJobs: # jobs to run after the tests are done @@ -124,10 +154,6 @@ stages: # config.json properties TCPConnectionString: $(SQL_TCP_CONN_STRING) NPConnectionString: $(SQL_NP_CONN_STRING) - AADAuthorityURL: $(AADAuthorityURL) - AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR) - AADServicePrincipalId: $(AADServicePrincipalId) - AADServicePrincipalSecret: $(AADServicePrincipalSecret) AzureKeyVaultUrl: $(AzureKeyVaultUrl) AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) SupportsIntegratedSecurity: $(SupportsIntegratedSecurity) @@ -142,7 +168,6 @@ stages: # instanceName: default: MSSQLSERVER # user: default: $(user) # saUser: default: $(saUser) - # password: default: $(password) # fileStreamDirectory: default: '' # x64AliasRegistryPath: default: $(x64AliasRegistryPath) # x86AliasRegistryPath: default: $(x86AliasRegistryPath) @@ -171,10 +196,6 @@ stages: # config.json properties TCPConnectionString: $(SQL_TCP_CONN_STRING) NPConnectionString: $(SQL_NP_CONN_STRING) - AADAuthorityURL: $(AADAuthorityURL) - AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR) - AADServicePrincipalId: $(AADServicePrincipalId) - AADServicePrincipalSecret: $(AADServicePrincipalSecret) AzureKeyVaultUrl: $(AzureKeyVaultUrl) AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) SupportsIntegratedSecurity: $(SupportsIntegratedSecurity) @@ -190,7 +211,6 @@ stages: # instanceName: default: MSSQLSERVER # user: default: $(user) # saUser: default: $(saUser) - # password: default: $(password) # fileStreamDirectory: default: '' # x64AliasRegistryPath: default: $(x64AliasRegistryPath) # x86AliasRegistryPath: default: $(x86AliasRegistryPath) @@ -218,10 +238,6 @@ stages: # config.json properties TCPConnectionString: $(SQL_TCP_CONN_STRING) NPConnectionString: $(SQL_NP_CONN_STRING) - AADAuthorityURL: $(AADAuthorityURL) - AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR) - AADServicePrincipalId: $(AADServicePrincipalId) - AADServicePrincipalSecret: $(AADServicePrincipalSecret) AzureKeyVaultUrl: $(AzureKeyVaultUrl) AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) SupportsIntegratedSecurity: $(SupportsIntegratedSecurity) @@ -251,10 +267,6 @@ stages: # config.json properties TCPConnectionString: $(SQL_TCP_CONN_STRING) NPConnectionString: $(SQL_NP_CONN_STRING) - AADAuthorityURL: $(AADAuthorityURL) - AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR) - AADServicePrincipalId: $(AADServicePrincipalId) - AADServicePrincipalSecret: $(AADServicePrincipalSecret) AzureKeyVaultUrl: $(AzureKeyVaultUrl) AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) SupportsIntegratedSecurity: $(SupportsIntegratedSecurity) @@ -310,9 +322,14 @@ stages: TCPConnectionString: $(AZURE_DB_TCP_CONN_STRING) NPConnectionString: $(AZURE_DB_NP_CONN_STRING) AADAuthorityURL: $(AADAuthorityURL) - AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR) + # Note: Using the isFork variable to determine if secrets are available is not ideal since + # it's an indirect association. But everything else (referencing secret variables various + # 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) - AADServicePrincipalSecret: $(AADServicePrincipalSecret) + ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: + AADServicePrincipalSecret: $(AADServicePrincipalSecret) AzureKeyVaultUrl: $(AzureKeyVaultUrl) AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) SupportsIntegratedSecurity: false @@ -337,9 +354,11 @@ stages: TCPConnectionString: $(AZURE_DB_TCP_CONN_STRING_eastus) NPConnectionString: $(AZURE_DB_NP_CONN_STRING_eastus) AADAuthorityURL: $(AADAuthorityURL) - AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR_eastus) + ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: + AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR_eastus) AADServicePrincipalId: $(AADServicePrincipalId) - AADServicePrincipalSecret: $(AADServicePrincipalSecret) + ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: + AADServicePrincipalSecret: $(AADServicePrincipalSecret) AzureKeyVaultUrl: $(AzureKeyVaultUrl) AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) SupportsIntegratedSecurity: false @@ -347,35 +366,36 @@ stages: LocalDbAppName: $(LocalDbAppName) LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) - 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) - AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR) - AADServicePrincipalId: $(AADServicePrincipalId) - AADServicePrincipalSecret: $(AADServicePrincipalSecret) - AzureKeyVaultUrl: $(AzureKeyVaultUrl) - AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) - SupportsIntegratedSecurity: $(SupportsIntegratedSecurity) - UserManagedIdentityClientId: $(UserManagedIdentityClientId) - AliasName: $(SQLAliasName) - 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: @@ -394,7 +414,6 @@ stages: # config.json properties TCPConnectionString: $(SQL_TCP_CONN_STRING) NPConnectionString: $(SQL_NP_CONN_STRING) - AADServicePrincipalSecret: $(AADServicePrincipalSecret) AzureKeyVaultUrl: $(AzureKeyVaultUrl) AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) SupportsIntegratedSecurity: false @@ -421,9 +440,11 @@ stages: TCPConnectionString: $(AZURE_DB_TCP_CONN_STRING) NPConnectionString: $(AZURE_DB_NP_CONN_STRING) AADAuthorityURL: $(AADAuthorityURL) - AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR) + ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: + AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR) AADServicePrincipalId: $(AADServicePrincipalId) - AADServicePrincipalSecret: $(AADServicePrincipalSecret) + ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: + AADServicePrincipalSecret: $(AADServicePrincipalSecret) AzureKeyVaultUrl: $(AzureKeyVaultUrl) AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) SupportsIntegratedSecurity: false @@ -431,53 +452,53 @@ stages: LocalDbAppName: $(LocalDbAppName) LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) - linux_enclave_sql: - pool: ADO-CI-AE-1ES-Pool - images: - Ubuntu20_Enclave_Sql19: ADO-UB20-Sql22 - TargetFrameworks: ${{parameters.targetFrameworksLinux }} - netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} - buildPlatforms: [AnyCPU] - testSets: [AE] - useManagedSNI: [true] - codeCovTargetFrameworks: ${{parameters.codeCovTargetFrameworks }} - configSqlFor: enclave - operatingSystem: Linux - 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) - AADPasswordConnectionString: $(AAD_PASSWORD_CONN_STR) - AADServicePrincipalId: $(AADServicePrincipalId) - AADServicePrincipalSecret: $(AADServicePrincipalSecret) - AzureKeyVaultUrl: $(AzureKeyVaultUrl) - AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) - SupportsIntegratedSecurity: false - UserManagedIdentityClientId: $(UserManagedIdentityClientId) - LocalDbAppName: $(LocalDbAppName) - LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) - - # Azure Sql Server - Mac - mac_azure_sql: + ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: # only run enclave jobs if the password is available + linux_enclave_sql: + pool: ADO-CI-AE-1ES-Pool + images: + Ubuntu20_Enclave_Sql19: ADO-UB20-Sql22 + TargetFrameworks: ${{parameters.targetFrameworksLinux }} + netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} + buildPlatforms: [AnyCPU] + testSets: [AE] + useManagedSNI: [true] + codeCovTargetFrameworks: ${{parameters.codeCovTargetFrameworks }} + configSqlFor: enclave + operatingSystem: Linux + configProperties: + # config.json properties + TCPConnectionStringHGSVBS: $(SQL_TCP_CONN_STRING_HGSVBS) + TCPConnectionStringNoneVBS: $(SQL_TCP_CONN_STRING_NoneVBS) + TCPConnectionStringAASSGX: $(SQL_TCP_CONN_STRING_AASSGX) + EnclaveEnabled: true + AADServicePrincipalId: $(AADServicePrincipalId) + ${{ if eq(variables['system.pullRequest.isFork'], 'False') }}: + AADServicePrincipalSecret: $(AADServicePrincipalSecret) + AzureKeyVaultUrl: $(AzureKeyVaultUrl) + AzureKeyVaultTenantId: $(AzureKeyVaultTenantId) + SupportsIntegratedSecurity: false + UserManagedIdentityClientId: $(UserManagedIdentityClientId) + LocalDbAppName: $(LocalDbAppName) + LocalDbSharedInstanceName: $(LocalDbSharedInstanceName) + + # Self hosted SQL Server on Mac + mac_sql_22: pool: $(defaultHostedPoolName) hostedPool: true images: - MacOSLatest_Azure_Sql: macos-latest + MacOSLatest_Sql22: macos-latest TargetFrameworks: ${{parameters.targetFrameworksLinux }} netcoreVersionTestUtils: ${{parameters.netcoreVersionTestUtils }} buildPlatforms: [AnyCPU] testSets: ${{parameters.testSets }} useManagedSNI: [true] codeCovTargetFrameworks: ${{parameters.codeCovTargetFrameworks }} - configSqlFor: azure + configSqlFor: local operatingSystem: Mac configProperties: # config.json properties - TCPConnectionString: $(AZURE_DB_SP_TCP_CONN_STRING) - NPConnectionString: $(AZURE_DB_SP_NP_CONN_STRING) + TCPConnectionString: $(SQL_TCP_CONN_STRING) + NPConnectionString: $(SQL_NP_CONN_STRING) SupportsIntegratedSecurity: false ManagedIdentitySupported: false LocalDbAppName: $(LocalDbAppName) diff --git a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml index 124741642c..c5448be6bc 100644 --- a/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml +++ b/eng/pipelines/dotnet-sqlclient-signing-pipeline.yml @@ -45,11 +45,6 @@ parameters: # parameters are shown up in ADO UI in a build queue time type: boolean default: false -- name: MDS_PackageRef_Version - displayName: 'MDS package version of AKV Provider (build AKV)' - type: string - default: 5.1.5 - - name: CurrentNetFxVersion displayName: 'Lowest supported .NET Framework version (MDS validation)' type: string @@ -73,8 +68,6 @@ variables: value: drop_buildMDS_build_signed_package - name: PublishSymbols value: ${{ parameters['publishSymbols'] }} - - name: MDS_PackageRef_Version - value: ${{ parameters['MDS_PackageRef_Version'] }} - name: CurrentNetFxVersion value: ${{ parameters['CurrentNetFxVersion'] }} @@ -89,16 +82,26 @@ extends: template: v2/OneBranch.${{parameters.oneBranchType }}.CrossPlat.yml@templates # https://aka.ms/obpipelines/templates parameters: featureFlags: + # Suggested by MerlinBot (https://sqlclientdrivers.visualstudio.com/ADO.Net/_git/dotnet-sqlclient/pullrequest/4882) + EnableCDPxPAT: false WindowsHostVersion: 1ESWindows2022 globalSdl: # https://aka.ms/obpipelines/sdl + tsa: + # The OneBranch template will set 'break' to false for the other SDL + # tools when TSA is enabled. This allows TSA to gather the results + # and publish them for downstream analysis. + enabled: true apiscan: - enabled: ${{ not(parameters['isPreview']) }} + enabled: true + # For non-official builds, the OneBranch template seems to set APIScan's + # 'break' to true even when TSA is enabled. We don't want APIScan to + # break non-official builds, so we explicitly set 'break' to false here. + ${{ if ne(parameters.oneBranchType, 'Official') }}: + break: false softwareFolder: $(softwareFolder) symbolsFolder: $(symbolsFolder) softwarename: Microsoft.Data.SqlClient versionNumber: $(AssemblyFileVersion) - tsa: - enabled: ${{ not(parameters['isPreview']) }} # onebranch publish all sdl results to TSA. If TSA is disabled all SDL tools will forced into 'break' build mode. codeql: compiled: enabled: ${{ not(parameters['isPreview']) }} @@ -130,15 +133,6 @@ extends: tsaOptionsPath: $(REPOROOT)\.config\tsaoptions.json disableLegacyManifest: true stages: - - stage: buildAKV - displayName: 'Build AKV Provider' - jobs: - - template: eng/pipelines/common/templates/jobs/build-signed-akv-package-job.yml@self - parameters: - symbolsFolder: $(symbolsFolder) - softwareFolder: $(softwareFolder) - publishSymbols: ${{ parameters['publishSymbols'] }} - - stage: buildMDS displayName: 'Build MDS' jobs: diff --git a/eng/pipelines/jobs/build-akv-official-job.yml b/eng/pipelines/jobs/build-akv-official-job.yml new file mode 100644 index 0000000000..a4374b773b --- /dev/null +++ b/eng/pipelines/jobs/build-akv-official-job.yml @@ -0,0 +1,154 @@ +################################################################################# +# 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. # +################################################################################# + +parameters: + - name: apiScanDllPath + type: string + + - name: apiScanPdbPath + type: string + + - name: assemblyFileVersion + type: string + + - name: buildConfiguration + type: string + + - name: nugetPackageVersion + type: string + + - name: mdsPackageVersion + type: string + + - name: publishSymbols + type: boolean + + - name: signingAppRegistrationClientId + type: string + + - name: signingAppRegistrationTenantId + type: string + + - name: signingAuthAkvName + type: string + + - name: signingAuthSignCertName + type: string + + - name: signingEsrpClientId + type: string + + - name: signingEsrpConnectedServiceName + type: string + + - name: symbolsAzureSubscription + type: string + + - name: symbolsPublishProjectName + type: string + + - name: symbolsPublishServer + type: string + + - name: symbolsPublishTokenUri + type: string + + - name: symbolsUploadAccount + type: string + + # @TODO: This should be determined from build output, or at a higher level + - # Note: not intended to be passed in, is only used for copying files for ApiScan. + # This is only defined as a parameter since ADO pipelines do not support array variables. + name: targetFrameworks + type: object + default: + - net462 + - net8.0 + - net9.0 + +jobs: + - job: buildSignedAkvPackage + displayName: 'Build Signed AKV Package' + pool: + type: windows + + variables: + ob_outputDirectory: '$(ARTIFACT_PATH)' + + steps: + - template: ../steps/script-output-environment-variables-step.yml@self + + - powershell: | + $jsonParams = '${{ convertToJson(parameters) }}' -replace '\\', '\\' + $jsonParams | ConvertFrom-Json | Format-List + displayName: 'Output Job Parameters' + + - template: ../steps/compound-build-akv-step.yml@self + parameters: + assemblyFileVersion: '${{ parameters.assemblyFileVersion }}' + buildConfiguration: '${{ parameters.buildConfiguration }}' + mdsPackageVersion: '${{ parameters.mdsPackageVersion }}' + + - ${{ each targetFramework in parameters.targetFrameworks }}: + - template: ../steps/compound-extract-akv-apiscan-files-step.yml + parameters: + buildConfiguration: '${{ parameters.buildConfiguration }}' + dllPath: '${{ parameters.apiScanDllPath }}' + pdbPath: '${{ parameters.apiScanPdbPath }}' + referenceType: Package + targetFramework: '${{ targetFramework }}' + + - template: ../steps/roslyn-analyzers-akv-step.yml@self + parameters: + buildConfiguration: '${{ parameters.buildConfiguration }}' + mdsPackageVersion: '${{ parameters.mdsPackageVersion }}' + + - template: ../steps/compound-esrp-code-signing-step.yml@self + parameters: + appRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' + appRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' + artifactType: 'dll' + authAkvName: '${{ parameters.signingAuthAkvName }}' + authSignCertName: '${{ parameters.signingAuthSignCertName }}' + esrpClientId: '${{ parameters.signingEsrpClientId }}' + esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' + + - template: ../steps/compound-nuget-pack-step.yml@self + parameters: + buildConfiguration: '${{ parameters.buildConfiguration }}' + generateSymbolsPackage: true # Always generate symbols, even if they are not published + packageVersion: '${{ parameters.nugetPackageVersion }}' + nuspecPath: '$(REPO_ROOT)/tools/specs/add-ons/$(PACKAGE_NAME).nuspec' + outputDirectory: '$(ARTIFACT_PATH)' + referenceType: 'Package' + + - template: ../steps/compound-esrp-code-signing-step.yml@self + parameters: + appRegistrationClientId: '${{ parameters.signingAppRegistrationClientId }}' + appRegistrationTenantId: '${{ parameters.signingAppRegistrationTenantId }}' + artifactType: 'pkg' + authAkvName: '${{ parameters.signingAuthAkvName }}' + authSignCertName: '${{ parameters.signingAuthSignCertName }}' + esrpClientId: '${{ parameters.signingEsrpClientId }}' + esrpConnectedServiceName: '${{ parameters.signingEsrpConnectedServiceName }}' + + - ${{ 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)' + azureSubscription: '${{ parameters.symbolsAzureSubscription }}' + publishProjectName: '${{ parameters.symbolsPublishProjectName }}' + packageName: '$(PACKAGE_NAME)' + publishServer: '${{ parameters.symbolsPublishServer }}' + publishToInternal: true + publishToPublic: true + publishTokenUri: '${{ parameters.symbolsPublishTokenUri }}' + referenceType: 'Package' + searchPattern: | + Windows_NT/${{ parameters.buildConfiguration }}.AnyCPU/AzureKeyVaultProvider/**/$(PACKAGE_NAME).pdb + AnyOS/${{ parameters.buildConfiguration }}.AnyCPU/AzureKeyVaultProvider/**/$(PACKAGE_NAME).pdb + uploadAccount: '${{ parameters.symbolsUploadAccount }}' + version: '${{ parameters.nugetPackageVersion }}' diff --git a/eng/pipelines/libraries/build-variables.yml b/eng/pipelines/libraries/build-variables.yml index 1c26f26a69..3dd87fe7d7 100644 --- a/eng/pipelines/libraries/build-variables.yml +++ b/eng/pipelines/libraries/build-variables.yml @@ -6,5 +6,4 @@ variables: - template: common-variables.yml@self - - template: akv-variables.yml@self - template: mds-variables.yml@self diff --git a/eng/pipelines/libraries/common-variables.yml b/eng/pipelines/libraries/common-variables.yml index 7b2bc00cb1..b1b5d5ce7f 100644 --- a/eng/pipelines/libraries/common-variables.yml +++ b/eng/pipelines/libraries/common-variables.yml @@ -5,6 +5,14 @@ ################################################################################# variables: + - group: ESRP Federated Creds (AME) + # ESRPConnectedServiceName + # ESRPClientId + # AppRegistrationClientId + # AppRegistrationTenantId + # AuthAKVName + # AuthSignCertName + - name: Configuration value: Release - name: CommitHead @@ -17,7 +25,26 @@ variables: value: $(REPOROOT)/symbols - name: artifactDirectory value: '$(REPOROOT)/packages' - - name: appRegistrationClientId - value: 'a0d18a38-fde1-4ba7-92e1-15be16cb6a8e' - - name: appRegistrationTenantId - value: '72f988bf-86f1-41af-91ab-2d7cd011db47' + + # Update this after every release. This is used to generate the MDS NuGet package version. + - name: Major + value: '6' + - name: Minor + value: '1' + - name: Patch + value: '1' + + # Update this for preview releases. + - name: Preview + value: '-preview' + - name: Revision + value: '3' + + - name: NugetPackageVersion + value: $(Major).$(Minor).$(Patch) + - name: PreviewNugetPackageVersion + value: $(Major).$(Minor).$(Patch)$(Preview)$(Revision).$(Build.BuildNumber) + - name: AssemblyFileVersion + value: '$(Major).$(Minor)$(Patch).$(Build.BuildNumber)' + - 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 afdd8ff6fe..d7723a059f 100644 --- a/eng/pipelines/libraries/mds-validation-variables.yml +++ b/eng/pipelines/libraries/mds-validation-variables.yml @@ -17,7 +17,7 @@ variables: - name: expectedFolderNames value: lib,ref,runtimes - name: expectedDotnetVersions - value: net462,net8.0,net9.0 + value: netstandard2.0,net462,net8.0,net9.0 - name: Database value: Northwind - name: platform diff --git a/eng/pipelines/libraries/mds-variables.yml b/eng/pipelines/libraries/mds-variables.yml index 551e9b63a9..e0e2cd1abf 100644 --- a/eng/pipelines/libraries/mds-variables.yml +++ b/eng/pipelines/libraries/mds-variables.yml @@ -6,12 +6,3 @@ variables: - group: Release Variables - - - name: NugetPackageVersion - value: $(Major).$(Minor).$(Patch) - - name: PreviewNugetPackageVersion - value: $(Major).$(Minor).$(Patch)$(Preview)$(Revision).$(Build.BuildNumber) - - name: AssemblyFileVersion - value: '$(Major).$(Minor)$(Patch).$(Build.BuildNumber)' - - name: nuspecPath - value: '$(REPOROOT)/tools/specs/Microsoft.Data.SqlClient.nuspec' diff --git a/eng/pipelines/steps/compound-build-akv-step.yml b/eng/pipelines/steps/compound-build-akv-step.yml new file mode 100644 index 0000000000..906dcfaf72 --- /dev/null +++ b/eng/pipelines/steps/compound-build-akv-step.yml @@ -0,0 +1,54 @@ +################################################################################# +# 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. # +################################################################################# + +# @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 + +parameters: + - name: assemblyFileVersion + type: string + + - name: buildConfiguration + type: string + + - name: mdsPackageVersion + type: string + +steps: + - task: DownloadSecureFile@1 + displayName: 'Download Signing Key' + inputs: + retryCount: 5 + secureFile: 'netfxKeypair.snk' + + - task: UseDotNet@2 + displayName: 'Install .NET 9.x SDK' + inputs: + packageType: 'sdk' + version: '9.x' + + - task: UseDotNet@2 + displayName: 'Install .NET 8.x Runtime' + inputs: + packageType: 'runtime' + version: '8.x' + + - task: MSBuild@1 + displayName: 'Build.proj - BuildAkv' + inputs: + solution: '$(REPO_ROOT)/build.proj' + configuration: '${{ parameters.buildConfiguration }}' + msbuildArguments: >- + -t:BuildAkv + -p:AssemblyFileVersion=${{ parameters.assemblyFileVersion }} + -p:NugetPackageVersion=${{ parameters.mdsPackageVersion }} + -p:ReferenceType=Package + -p:SigningKeyPath=$(Agent.TempDirectory)/netfxKeypair.snk + + - script: tree /a /f $(BUILD_OUTPUT) + displayName: Output Build Output Tree diff --git a/eng/pipelines/steps/compound-esrp-code-signing-step.yml b/eng/pipelines/steps/compound-esrp-code-signing-step.yml new file mode 100644 index 0000000000..1eedc78c48 --- /dev/null +++ b/eng/pipelines/steps/compound-esrp-code-signing-step.yml @@ -0,0 +1,145 @@ +################################################################################# +# 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. # +################################################################################# + +parameters: + - name: appRegistrationClientId + type: string + + - name: appRegistrationTenantId + type: string + + - # Name of the Azure Key Vault to retrieve certificates from. + # note: This has nothing to do with the AKV provider package. + name: authAkvName + type: string + + - name: authSignCertName + type: string + + - name: artifactType + type: string + values: + - dll + - pkg + + - name: esrpConnectedServiceName + type: string + + - name: esrpClientId + type: string + +steps: + - ${{ if eq(parameters.artifactType, 'dll') }}: + - task: EsrpMalwareScanning@5 + displayName: 'ESRP Malware Scanning Code' + inputs: + AppRegistrationClientId: '${{ parameters.appRegistrationClientId }}' + AppRegistrationTenantId: '${{ parameters.appRegistrationTenantId }}' + CleanupTempStorage: 1 + ConnectedServiceName: '${{ parameters.esrpConnectedServiceName }}' + EsrpClientId: '${{ parameters.esrpClientId }}' + FolderPath: '$(BUILD_OUTPUT)' + Pattern: '*.dll' + UseMSIAuthentication: true + VerboseLogin: 1 + + - task: EsrpCodeSigning@5 + displayName: 'ESRP Signing Code' + inputs: + AppRegistrationClientId: '${{ parameters.appRegistrationClientId }}' + AppRegistrationTenantId: '${{ parameters.appRegistrationTenantId }}' + AuthAKVName: '${{ parameters.authAkvName }}' + AuthSignCertName: '${{ parameters.authSignCertName }}' + ConnectedServiceName: '${{ parameters.esrpConnectedServiceName }}' + EsrpClientId: '${{ parameters.esrpClientId }}' + FolderPath: '$(BUILD_OUTPUT)' + Pattern: '*.dll' + signConfigType: 'inlineSignParams' + UseMSIAuthentication: true + inlineOperation: | + [ + { + "keyCode": "CP-230012", + "operationSetCode": "SigntoolSign", + "parameters": + [ + { + "parameterName": "OpusName", + "parameterValue": "Microsoft Data SqlClient Data Provider for SQL Server" + }, + { + "parameterName": "OpusInfo", + "parameterValue": "http://www.microsoft.com" + }, + { + "parameterName": "FileDigest", + "parameterValue": "/fd \"SHA256\"" + }, + { + "parameterName": "PageHash", + "parameterValue": "/NPH" + }, + { + "parameterName": "TimeStamp", + "parameterValue": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256" + } + ], + "toolName": "sign", + "toolVersion": "1.0" + }, + { + "keyCode": "CP-230012", + "operationSetCode": "SigntoolVerify", + "parameters": [ ], + "toolName": "sign", + "toolVersion": "1.0" + } + ] + + - ${{ else }}: + - task: EsrpMalwareScanning@5 + displayName: 'ESRP Malware Scanning NuGet Package' + inputs: + AppRegistrationClientId: '${{ parameters.appRegistrationClientId }}' + AppRegistrationTenantId: '${{ parameters.appRegistrationTenantId }}' + CleanupTempStorage: 1 + ConnectedServiceName: '${{ parameters.esrpConnectedServiceName }}' + EsrpClientId: '${{ parameters.esrpClientId }}' + FolderPath: '$(ARTIFACT_PATH)' + Pattern: '*.*nupkg' + UseMSIAuthentication: true + VerboseLogin: 1 + + - task: EsrpCodeSigning@5 + displayName: 'ESRP Signing NuGet Package' + inputs: + AppRegistrationClientId: '${{ parameters.appRegistrationClientId }}' + AppRegistrationTenantId: '${{ parameters.appRegistrationTenantId }}' + ConnectedServiceName: '${{ parameters.esrpConnectedServiceName }}' + EsrpClientId: '${{ parameters.esrpClientId }}' + AuthAKVName: '${{ parameters.authAkvName }}' + AuthSignCertName: '${{ parameters.authSignCertName }}' + FolderPath: '$(ARTIFACT_PATH)' + Pattern: '*.*nupkg' + signConfigType: 'inlineSignParams' + UseMSIAuthentication: true + inlineOperation: | + [ + { + "keyCode": "CP-401405", + "operationSetCode": "NuGetSign", + "parameters": [ ], + "toolName": "sign", + "toolVersion": "1.0" + }, + { + "keyCode": "CP-401405", + "operationSetCode": "NuGetVerify", + "parameters": [ ], + "toolName": "sign", + "toolVersion": "1.0" + } + ] diff --git a/eng/pipelines/steps/compound-extract-akv-apiscan-files-step.yml b/eng/pipelines/steps/compound-extract-akv-apiscan-files-step.yml new file mode 100644 index 0000000000..ebd6743361 --- /dev/null +++ b/eng/pipelines/steps/compound-extract-akv-apiscan-files-step.yml @@ -0,0 +1,49 @@ +################################################################################# +# 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. # +################################################################################# + +# @TODO: This can be made generic when the output folders match the package names. + +parameters: + - name: buildConfiguration + type: string + values: + - Debug + - Release + + - # Path where the dll files should be copied + name: dllPath + type: string + + - # Path where the pdb files should be copied + name: pdbPath + type: string + + - name: referenceType + type: string + values: + - Package + - Project + + - # Short form of the target framework version to extract. Used to build the path to the files + # to extract. Eg, net462 + name: targetFramework + type: string + +steps: + - task: CopyFiles@2 + displayName: 'Copy ${{ parameters.targetFramework }} DLL' + inputs: + contents: 'Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.dll' + sourceFolder: '$(BUILD_OUTPUT)/${{ parameters.referenceType }}/bin/Windows_NT/${{ parameters.buildConfiguration }}.AnyCPU/AzureKeyVaultProvider/${{ parameters.targetFramework }}/' + targetFolder: '${{ parameters.dllPath }}/win/${{ parameters.targetFramework }}/' + + - task: CopyFiles@2 + displayName: 'Copy ${{ parameters.targetFramework }} PDB' + inputs: + contents: 'Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.pdb' + sourceFolder: '$(BUILD_OUTPUT)/${{ parameters.referenceType }}/bin/Windows_NT/${{ parameters.buildConfiguration }}.AnyCPU/AzureKeyVaultProvider/${{ parameters.targetFramework }}/' + targetFolder: '${{ parameters.pdbPath }}/win/${{ parameters.targetFramework }}/' + diff --git a/eng/pipelines/steps/compound-nuget-pack-step.yml b/eng/pipelines/steps/compound-nuget-pack-step.yml new file mode 100644 index 0000000000..2a1ba81516 --- /dev/null +++ b/eng/pipelines/steps/compound-nuget-pack-step.yml @@ -0,0 +1,59 @@ +################################################################################# +# 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. # +################################################################################# + +parameters: + - name: buildConfiguration + type: string + + - name: generateSymbolsPackage + type: boolean + + - name: packageVersion + type: string + + - name: nuspecPath + type: string + + - name: outputDirectory + type: string + + - name: referenceType + type: string + values: + - Package + - Project + +steps: + - task: NuGetToolInstaller@1 + displayName: 'Install Latest Nuget' + inputs: + checkLatest: true + + - ${{ if parameters.generateSymbolsPackage }}: + - task: NuGetCommand@2 + displayName: 'Generate NuGet Package and Symbols Package' + inputs: + command: custom + arguments: >- + pack + ${{ parameters.nuspecPath }} + -Symbols + -SymbolPackageFormat snupkg + -Version ${{ parameters.packageVersion }} + -OutputDirectory ${{ parameters.outputDirectory }} + -Properties "COMMITID=$(Build.SourceVersion);Configuration=${{ parameters.buildConfiguration }};ReferenceType=${{ parameters.referenceType }}" + - ${{ else }}: + - task: NuGetCommand@2 + displayName: 'Generate NuGet Package' + inputs: + command: custom + arguments: >- + pack + ${{ parameters.nuspecPath }} + -Version ${{ parameters.packageVersion }} + -OutputDirectory ${{ parameters.outputDirectory }} + -Properties "COMMITID=$(Build.SourceVersion);Configuration=${{ parameters.buildConfiguration }};ReferenceType=${{ parameters.referenceType }}" + diff --git a/eng/pipelines/steps/compound-publish-symbols-step.yml b/eng/pipelines/steps/compound-publish-symbols-step.yml new file mode 100644 index 0000000000..5afda0182a --- /dev/null +++ b/eng/pipelines/steps/compound-publish-symbols-step.yml @@ -0,0 +1,163 @@ +################################################################################# +# 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. # +################################################################################# + +# For more details, see https://www.osgwiki.com/wiki/Symbols_Publishing_Pipeline_to_SymWeb_and_MSDL + +parameters: + - # Name of the symbols artifact that will be published + name: artifactName + type: string + + - # Azure subscription where the publishing task will execute + name: azureSubscription + type: string + + - # Package name, typically the name of the nuget package being built + name: packageName + type: string + + - # Project that symbols will belong to (decided during symbols onboarding) + name: publishProjectName + type: string + + - # Where symbols publishing service is hosted, will be prepended to trafficmanager.net + name: publishServer + type: string + + - # Whether to publish the uploaded symbols to the internal symbols servers + name: publishToInternal + type: boolean + + - # Whether to publish the uploaded symbols to the public symbols servers + name: publishToPublic + type: boolean + + - # URI to use for requesting a bearer-token for publishing the symbols + name: publishTokenUri + type: string + + - # Type of build, used to determine path to search for symbols files + # @TODO: Can we remove this? + name: referenceType + type: string + values: + - Project + - Package + + - # Pattern to use to search for pdb symbols files to upload/publish + name: searchPattern + type: string + + - # Account/org where the symbols will be uploaded + name: uploadAccount + type: string + + - # Version of the symbols to publish, typically the same as the NuGet package version + name: version + type: string + +steps: + - # Set variable for downstream tasks (allegedly) + # Note: Because variables cannot be set in top-level of template, this has to be done during + # runtime. + script: 'echo ##vso[task.setvariable variable=ArtifactServices.Symbol.AccountName;]${{ parameters.uploadAccount }}' + displayName: 'Set ArtifactServices.Symbol.AccountName to ${{ parameters.uploadAccount }}' + + - task: PublishSymbols@2 + displayName: 'Upload symbols to ${{ parameters.uploadAccount }} org' + inputs: + IndexSources: false + Pat: '$(System.AccessToken)' + SearchPattern: '${{ parameters.searchPattern }}' + SymbolExpirationInDays: 1825 # 5 years + SymbolServerType: 'TeamServices' + SymbolsArtifactName: '${{ parameters.artifactName }}' + SymbolsFolder: '$(BUILD_OUTPUT)/${{ parameters.referenceType }}/bin' + SymbolsMaximumWaitTime: 60 + SymbolsProduct: '${{ parameters.packageName }}' + SymbolsVersion: '${{ parameters.version }}' + + - task: AzureCLI@2 + displayName: 'Publish Symbols' + inputs: + azureSubscription: '${{ parameters.azureSubscription }}' + scriptLocation: inlineScript + scriptType: ps + inlineScript: | + # Propagate parameters to PS variables ################################################ + $artifactName = "${{ parameters.artifactName }}" + echo "artifactName= $artifactName" + + $publishProjectName = "${{ parameters.publishProjectName }}" + echo "publishProjectName= $publishProjectName" + + $publishToInternal = "${{ parameters.publishToInternal }}".ToLower() + echo "publishToInternal= $publishToInternal" + + $publishToPublic = "${{ parameters.publishToPublic }}".ToLower() + echo "publishToPublic= $publishToPublic" + + $publishServer = "${{ parameters.publishServer }}" + echo "publishServer= $publishServer" + + $publishTokenUri = "${{ parameters.publishTokenUri }}" + echo "publishTokenUri= $publishTokenUri" + + # Publish symbols ##################################################################### + # 1) Get the access token for the symbol publishing service + echo "> 1.Acquiring symbol publishing token..." + $symbolPublishingToken = az account get-access-token --resource $publishTokenUri --query accessToken -o tsv + echo "> 1.Symbol publishing token acquired." + + # 2) Register the request name + echo "> 2.Registering request name..." + $requestNameRegistrationBody = "{'requestName': '$artifactName'}" + Invoke-RestMethod ` + -Method POST ` + -Uri "https://$publishServer.trafficmanager.net/projects/$publishProjectName/requests" ` + -Headers @{ Authorization = "Bearer $symbolPublishingToken" } ` + -ContentType "application/json" ` + -Body $requestNameRegistrationBody + echo "> 2.Request name registered successfully." + + # 3) Publish the symbols + echo "> 3.Submitting request to publish symbols..." + $publishSymbolsBody = "{'publishToInternalServer': $publishToInternal, 'publishToPublicServer': $publishToPublic}" + Invoke-RestMethod ` + -Method POST ` + -Uri "https://$publishServer.trafficmanager.net/projects/$publishProjectName/requests/$artifactName" ` + -Headers @{ Authorization = "Bearer $symbolPublishingToken" } ` + -ContentType "application/json" ` + -Body $publishSymbolsBody + echo "> 3.Request to publish symbols submitted successfully." + + # The following REST calls are used to check publishing status. + echo "> 4.Checking the status of the request ..." + Invoke-RestMethod ` + -Method GET ` + -Uri "https://$publishServer.trafficmanager.net/projects/$publishProjectName/requests/$artifactName" ` + -Headers @{ Authorization = "Bearer $symbolPublishingToken" } ` + -ContentType "application/json" + + echo "Use below tables to interpret the values of xxxServerStatus and xxxServerResult fields from the response." + + echo "PublishingStatus" + echo "-----------------" + echo "0 NotRequested; The request has not been requested to publish." + echo "1 Submitted; The request is submitted to be published" + echo "2 Processing; The request is still being processed" + echo "3 Completed; The request has been completed processing. It can be failed or successful. Check PublishingResult to get more details" + + echo "PublishingResult" + echo "-----------------" + echo "0 Pending; The request has not completed or has not been requested." + echo "1 Succeeded; The request has published successfully" + echo "2 Failed; The request has failed to publish" + echo "3 Cancelled; The request was cancelled" + + + + diff --git a/eng/pipelines/steps/roslyn-analyzers-akv-step.yml b/eng/pipelines/steps/roslyn-analyzers-akv-step.yml new file mode 100644 index 0000000000..0e05177d5a --- /dev/null +++ b/eng/pipelines/steps/roslyn-analyzers-akv-step.yml @@ -0,0 +1,31 @@ +################################################################################# +# Licensed to the .NET Foundation under one or more agreements. # +# The .NET Foundation licenses this file to you under the MIT license. # +# See the LICENSE file in the project root for more information. # +################################################################################# + +# @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. + +parameters: + - name: buildConfiguration + type: string + + - name: mdsPackageVersion + type: string + +steps: + - task: securedevelopmentteam.vss-secure-development-tools.build-task-roslynanalyzers.RoslynAnalyzers@3 + displayName: 'Roslyn Analyzers' + inputs: + msBuildArchitecture: x64 + msBuildCommandLine: >- + msbuild + $(REPO_ROOT)/build.proj + -t:BuildAkv + -p:Configuration=${{ parameters.buildConfiguration }} + -p:NugetPackageVersion=${{ parameters.mdsPackageVersion }} + -p:ReferenceType=Package + msBuildVersion: 17.0 + setupCommandLinePicker: vs2022 diff --git a/eng/pipelines/libraries/akv-variables.yml b/eng/pipelines/steps/script-output-environment-variables-step.yml similarity index 53% rename from eng/pipelines/libraries/akv-variables.yml rename to eng/pipelines/steps/script-output-environment-variables-step.yml index 259ae19dba..2396cb835c 100644 --- a/eng/pipelines/libraries/akv-variables.yml +++ b/eng/pipelines/steps/script-output-environment-variables-step.yml @@ -4,12 +4,7 @@ # See the LICENSE file in the project root for more information. # ################################################################################# -variables: - - group: AKV Release Variables +steps: + - script: SET + displayName: 'Output Environment Variables' - - name: AKVNugetPackageVersion - value: $(AKVMajor).$(AKVMinor).$(AKVPatch) - - name: AKVAssemblyFileVersion - value: '$(AKVMajor).$(AKVMinor)$(AKVPatch).$(Build.BuildNumber)' - - name: akvNuspecPath - value: tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec diff --git a/eng/pipelines/variables/akv-official-variables.yml b/eng/pipelines/variables/akv-official-variables.yml new file mode 100644 index 0000000000..f30f0e67e4 --- /dev/null +++ b/eng/pipelines/variables/akv-official-variables.yml @@ -0,0 +1,44 @@ +################################################################################# +# 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. # +################################################################################# + +# @TODO: These seem to only really apply to official builds. Name should probably be adjusted to match. + +variables: + # @TODO: If symbols variables are indeed shared between projects and not expected to change + # then they can be removed from this variable group. + - group: 'akv-variables-v2' + # SymbolsAzureSubscription + # SymbolsPublishProjectName + # SymbolsPublishServer + # SymbolsPublishTokenUri + # SymbolsUploadAccount + + # Well-Known Variables ------------------------------------------------- + - name: PACKAGE_NAME + value: 'Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider' + + # Base Variables ------------------------------------------------------- + - name: mdsPackageVersion + value: '6.1.0' + + # @TODO: Version should ideally be pulled from one location (versions.props?) + - name: versionMajor + value: '6' + - name: versionMinor + value: '1' + - name: versionPatch + value: '0' + - name: versionPreview + value: '-preview1' + + # Compound Variables --------------------------------------------------- + - name: assemblyFileVersion + value: '${{ variables.versionMajor }}.${{ variables.versionMinor }}${{ variables.versionPatch }}.$(Build.BuildNumber)' + - name: nugetPackageVersion + value: '${{ variables.versionMajor }}.${{ variables.versionMinor }}.${{ variables.versionPatch }}' + #value: '${{ variables.versionMajor }}.${{ variables.versionMinor }}.${{ variables.versionPatch }}${{ variables.versionPreview }}' + + diff --git a/eng/pipelines/variables/common-variables.yml b/eng/pipelines/variables/common-variables.yml new file mode 100644 index 0000000000..1486c3c227 --- /dev/null +++ b/eng/pipelines/variables/common-variables.yml @@ -0,0 +1,19 @@ +################################################################################# +# 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. # +################################################################################# + +variables: + # Well-known paths + - name: REPO_ROOT + value: '$(Build.SourcesDirectory)' + readonly: true + + - name: BUILD_OUTPUT + value: '$(Build.SourcesDirectory)/artifacts' + readonly: true + + - name: ARTIFACT_PATH + value: '$(Build.SourcesDirectory)/packages' + readonly: true diff --git a/eng/pipelines/variables/esrp-signing-variables.yml b/eng/pipelines/variables/esrp-signing-variables.yml new file mode 100644 index 0000000000..15d920759f --- /dev/null +++ b/eng/pipelines/variables/esrp-signing-variables.yml @@ -0,0 +1,17 @@ +################################################################################# +# 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. # +################################################################################# + +# These variables are used for running ESRP signing tasks. All names start with "Signing" to make +# it clear that these variables are used for signing (as opposed to other msc tasks). + +variables: + - group: 'esrp-variables-v2' + # SigningAppRegistrationClientId + # SigningAppRegistrationTenantId + # SigningAuthAkvName + # SigningAuthSignCertName + # SigningEsrpClientId + # SigningEsrpConnectedServiceName diff --git a/eng/pipelines/variables/onebranch-variables.yml b/eng/pipelines/variables/onebranch-variables.yml new file mode 100644 index 0000000000..1a8ffadec5 --- /dev/null +++ b/eng/pipelines/variables/onebranch-variables.yml @@ -0,0 +1,21 @@ +################################################################################# +# 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. # +################################################################################# + +variables: + # Variables defined by us ---------------------------------------------- + - name: apiScanDllPath + value: '$(Build.SourcesDirectory)/apiScan/dlls' + + - name: apiScanPdbPath + value: '$(Build.SourcesDirectory)/apiScan/pdbs' + + # Variables defined by OneBranch --------------------------------------- + - name: Packaging.EnableSBOMSigning + value: true + + - # Docker image which is used to build the project https://aka.ms/obpipelines/containers + name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' diff --git a/override.ps1 b/override.ps1 new file mode 100644 index 0000000000..4c1a37983f --- /dev/null +++ b/override.ps1 @@ -0,0 +1,43 @@ +#Purpose: Updates SNI Version +Write-Host "SNI Version to test = 123" + +##Get the shared SNI Version from the downloaded artifact +#$SharedSNIVersion = Get-Content -path "$(Pipeline.Workspace)/SharedSNIVersion.txt" + +#Get the SNI Version to test from the user entered version. +$SharedSNIVersion = "123" + +# define file to update +$PropsPath = 'C:\Users\mdaigle\SqlClient\tools\props\Versions.props' +type $PropsPath + +# new version number to update to +##Write-Host "SNI Version to test = $(SNIValidationVersion)" +Write-Host "SNI Version to test = $SharedSNIVersion" + + +# define an xml object +$xml = New-Object XML + +# load content of xml from file defined above +$xml.Load($PropsPath) + +# define namespace used to read a node +$nsm = New-Object Xml.XmlNamespaceManager($xml.NameTable) +$nsm.AddNamespace('ns', $xml.DocumentElement.NamespaceURI) +$netFxSniVersion = $xml.SelectSingleNode('//ns:MicrosoftDataSqlClientSniVersion', $nsm) + +Write-Host "Node NetFx SNI Version = $($netFxSniVersion.InnerText)" + +# update the node inner text +$netFxSniVersion.InnerText = "$SharedSNIVersion" + +$netCoreSniVersion = $xml.SelectSingleNode('//ns:MicrosoftDataSqlClientSNIRuntimeVersion', $nsm) + +# update the node inner text +$netCoreSniVersion.InnerText = "$SharedSNIVersion" + +# save the xml file +$xml.Save($PropsPath) + +type $PropsPath diff --git a/policy/coding-best-practices.md b/policy/coding-best-practices.md new file mode 100644 index 0000000000..70ad4e3e75 --- /dev/null +++ b/policy/coding-best-practices.md @@ -0,0 +1,102 @@ +# Coding Best Practices + +This document describes some typical programming pitfalls and best practices +related to C# and .NET.  It will grow and change as we encounter new situations +and the codebase evolves. + +## Correctness & Business Logic + +- Validate if the code adheres to what it is supposed to do. Compare the + implementation with the specification and ask questions, to get clarity. +- Employ `Debug.Assert()` statements to validate program state, but never + rely on them to control program flow. For example, `Debug.Assert()` must + never be used to validate input. + +## Memory / Resource Management + +- C# – Rent large buffers from ArrayPool<T> and return them promptly to + cut down on heap allocations and improve throughput. Have the buffers been + returned to the pool? Will they be returned if an exception is thrown? +- Prefer Span<T> for stack allocated buffers. +- Double check the depth of the call stack if using async/await. Stack depth of + unwinding async/await state-machines of more than 3 calls can cause + performance issues. +- Resource cleanup Dispose(), Close(), or the equivalent to release files, + sockets, database connections, and other external resources. + +## Error Handling & Reliability  + +- Prefer return values to throwing exceptions.  Returns are strongly typed and + checked by the compiler. Consider patterns like + `bool TryXYZ(..., out Result result)` instead of `Result XYZ(...)`. +- Exceptions should be reserved for exceptional circumstances, like constructor + failure or out-of-memory, where the calling code stands little chance of + recovering. Routine, expected error conditions should not be expressed as + exceptions. +- Document all exceptions that may be thrown from an API. +- Do not let undocumented exceptions escape from an API. +- Be permissive with what you accept, and strict with what you produce. +- Consider Constrained Execution Regions for .NET Framework implementations. + +## Async, Concurrency & Thread Safety (if applicable) + +- Avoid calling Task.Result, Task.Wait() or Task.GetAwaiter().GetResult(). If + needed, exercise extreme caution. These can cause deadlocks. +- When awaiting a task with the await keyword, always use + Task.ConfigureAwait(false) so that the task continuation may run on any thread + rather than marshalling back to the original calling thread. + +## Backward Compatibility + +- Someone will always depend on your undocumented behaviour.  It is just as much + an API promise as explicitly documented behaviour. +- Use language features to deprecate/obsolete APIs, and to issue warnings for + notable upcoming breaking changes. + +## Security Considerations + +- Never log passwords, secrets, or connection strings with credentials. +- Validate inputs to avoid SQL injection, even on metadata calls. +- Are there any user inputs going into SQL or shell commands directly? +- Are secrets being logged or exposed in stack traces? +- Are TLS/certificate settings handled safely and explicitly? +- Are we sending unbounded data streams to server prior to authentication e.g. + in feature extensions? + +## Performance & Scalability  + +- For major features or large PRs, always run the internal performance + benchmarks or performance testsuite to determine if the new changes are + causing any performance degradation. + +## Observability (Logging / Tracing / Metrics)  + +- Error messages should contain enough context to understand the state of the + program and find the source code responsible for emitting it. +- Instrument changes for metrics/tracing/logging to aid future field diagnosis, + debugging, and app behaviour analysis. + +## Unit Tests / Integration + +- Have you added unit tests and integration tests? +- Unit tests should not depend on external resources.  Code under unit test + should permit mocked resources to be provided/injected. +- Avoid tests that rely on timing. + +## Configuration & Feature Flags  + +- Is the change considered a breaking change? If breaking change is not + avoidable, has a Configuration/Feature flag been introduced for customers to + revert to old behavior? + +## Code Coverage expectations + +- Ideally these expectations  should be codified into scanning tool + configuration that lives in the repo. +- Specific metrics will be chosen based on realistic analysis of existing + coverage and will be re-evaluated as testing matures. + +## Pipeline runs + +- Is the CI passing? If not, then have you looked at the failures, and found the + cause for failures? diff --git a/coding-style.md b/policy/coding-style.md similarity index 100% rename from coding-style.md rename to policy/coding-style.md diff --git a/policy/review-process.md b/policy/review-process.md new file mode 100644 index 0000000000..9216f722ae --- /dev/null +++ b/policy/review-process.md @@ -0,0 +1,74 @@ +# Review Process + +This document describes guidelines and responsibilities for participants of +pull request reviews. + +## Author Responsibilities + +- **Describe the problem up front.** In the PR description, include a concise, + self-contained summary of the issue being solved and link to relevant specs, + issues, or discussions. For open-source PRs, capture the context in one clear + section so reviewers don’t have to sift through comment threads. +- **Ship code with tests.** Submit unit and integration tests alongside the + implementation to demonstrate expected behavior and prevent regressions. +- **Instrument for production.** Add meaningful tracing or logging so the + changes can be monitored and debugged once deployed. +- **Keep PRs focused.** Split large or mixed-purpose changes into smaller, + logical units (e.g., separate refactors from feature work). +- **Highlight important changes.**  Add your own comments to the diffs to call + out important changes that require extra scrutiny, or to explain a change + whose inclusion isn't self-evident. +- **Choose your reviewers.**  The author should assign an initial set of + required and optional reviewers.  Reviewers may change their role later after + discussing with the author. +- **Any open issue blocks completion.**  Do not complete a review with open + issues.  All issues must be resolved by the reviewer who created them, or + another reviewer delegate.  Do not override tooling that prevents completion. +- **Do not resolve reviewer issues.**  Only reviewers should resolve reviewer + issues.  Ideally the creator of the issue should resolve it, but reviewers may + close each other's issues under certain circumstances. + +## Reviewer Responsibilities + +- **Understand what you’re approving.** If anything is unclear, ask questions. + Require in-code comments that explain why a solution exists - not just what it + does. PR comments are not a substitute for code comments. +- **Read the full PR description.** Context matters; don’t skip it. +- **Review the changes, not the design.** A code review is not the time to + review a design choice.  If you think an alternative design would be better, + discuss with the author and team, and ask for a new PR if the new design is + implemented instead. +- **Demand tests.** If appropriate tests are missing, request them. When + testing truly isn’t feasible, insist on a documented rationale. +- **Note partial reviews.** If you reviewed only part of the code, say so in + the PR. +- **Delegate wisely.** If you cannot complete your reviewer role's + responsibilities, delegate to another subject-matter expert. +- **Own your approval.** You are accountable for the quality and correctness of + the code you sign off on. +- **Block completion on all open issues.** Any issue you open against a review + must be resolved to your satisfaction before a review may complete. + Resolution may include new code changes to fix a problem, new in-code + documentation to explain something, or a discussion within the PR itself. +- **Do not close other reviewer's issues.** The reviewer who created an issue + should be the one to resolve it.  Exceptions include explicit delegation or + OOTO absences.  Any delegations should be discussed with the team. +- **Never rubber-stamp.** Trust the author but verify the code - always conduct + a real review. +- **Review in a timely manner.**  Reviewers should aim to perform a review + within 2 business days of receiving the initial request, and 1 business day + for follow-up changes.  Reviewing a PR is higher priority than most other + tasks. + +## Backwards Compatibility Awareness + +Subtle changes may have backwards compatibility implications.  Below are some of +the code changes and side-effects to watch out for: + +- Do these changes force updates to existing integration tests - a sign they may + introduce a breaking change? +- Will these changes modify the public API’s behavior in a way that could break + existing applications when they upgrade to the new binaries? +- For any non-security, breaking changes that impact customers, have we provided + an opt-out - such as a connection-string flag, AppContext switch, or similar + setting - that lets users revert to the previous behavior? diff --git a/release-notes/1.0/1.0.md b/release-notes/1.0/1.0.md deleted file mode 100644 index 16c2eff888..0000000000 --- a/release-notes/1.0/1.0.md +++ /dev/null @@ -1,18 +0,0 @@ -# Microsoft.Data.SqlClient 1.0 Release Notes - -The following Microsoft.Data.SqlClient 1.0 stable releases have been shipped: - -| Release Date | Description | Notes | -| :-- | :-- | :--: | -| 2019/09/26 | 1.0.19269.1 | [release notes](1.0.19269.1.md) | -| 2019/09/06 | 1.0.19249.1 | [release notes](1.0.19249.1.md) | -| 2019/08/27 | 1.0.19239.1 | [release notes](1.0.19239.1.md) | - -The following Microsoft.Data.SqlClient 1.0 preview releases have been shipped: - -| Release Date | Description | Notes | -| :-- | :-- | :--: | -| 2019/08/09 | 1.0.19221.1 Preview | [release notes](1.0.19221.1-Preview.md) | -| 2019/07/08 | 1.0.19189.1 Preview | [release notes](1.0.19189.1-Preview.md) | -| 2019/05/08 | 1.0.19128.1 Preview | [release notes](1.0.19128.1-Preview.md) | -| 2019/05/06 | 1.0.19123.2 Preview | [release notes](1.0.19123.2-Preview.md) | diff --git a/release-notes/1.1/1.1.md b/release-notes/1.1/1.1.md deleted file mode 100644 index 5ad6dca184..0000000000 --- a/release-notes/1.1/1.1.md +++ /dev/null @@ -1,18 +0,0 @@ -# Microsoft.Data.SqlClient 1.1 Releases - -The following Microsoft.Data.SqlClient 1.1 stable releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2021/03/10 | 1.1.4 | [release notes](1.1.4.md) | -| 2020/05/15 | 1.1.3 | [release notes](1.1.3.md) | -| 2020/04/15 | 1.1.2 | [release notes](1.1.2.md) | -| 2020/02/14 | 1.1.1 | [release notes](1.1.1.md) | -| 2019/11/20 | 1.1.0 | [release notes](1.1.0.md) | - -The following Microsoft.Data.SqlClient 1.1 preview releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2019/11/04 | 1.1.0-preview2.19309.1 | [release notes](1.1.0-preview2.md) | -| 2019/10/02 | 1.1.0-preview1.19275.1 | [release notes](1.1.0-preview1.md) | diff --git a/release-notes/2.0/2.0.md b/release-notes/2.0/2.0.md deleted file mode 100644 index 5bde012d6a..0000000000 --- a/release-notes/2.0/2.0.md +++ /dev/null @@ -1,17 +0,0 @@ -# Microsoft.Data.SqlClient 2.0 Releases - -The following Microsoft.Data.SqlClient 2.0 stable releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2020/08/25 | 2.0.1 | [release notes](2.0.1.md) | -| 2020/06/16 | 2.0.0 | [release notes](2.0.0.md) | - -The following Microsoft.Data.SqlClient 2.0 preview releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2020/01/21 | 2.0.0-preview1.20021.1 | [release notes](2.0.0-preview1.md) | -| 2020/03/24 | 2.0.0-preview2.20084.1 | [release notes](2.0.0-preview2.md) | -| 2020/05/01 | 2.0.0-preview3.20122.2 | [release notes](2.0.0-preview3.md) | -| 2020/05/21 | 2.0.0-preview4.20142.4 | [release notes](2.0.0-preview4.md) | diff --git a/release-notes/2.1/2.1.md b/release-notes/2.1/2.1.md deleted file mode 100644 index 395c19e60e..0000000000 --- a/release-notes/2.1/2.1.md +++ /dev/null @@ -1,21 +0,0 @@ -# Microsoft.Data.SqlClient 2.1 Releases - -The following Microsoft.Data.SqlClient 2.1 stable releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2024/01/09 | 2.1.7 | [release notes](2.1.7.md) | -| 2023/04/27 | 2.1.6 | [release notes](2.1.6.md) | -| 2022/08/30 | 2.1.5 | [release notes](2.1.5.md) | -| 2021/09/20 | 2.1.4 | [release notes](2.1.4.md) | -| 2021/05/21 | 2.1.3 | [release notes](2.1.3.md) | -| 2021/03/03 | 2.1.2 | [release notes](2.1.2.md) | -| 2020/12/18 | 2.1.1 | [release notes](2.1.1.md) | -| 2020/11/19 | 2.1.0 | [release notes](2.1.0.md) | - -The following Microsoft.Data.SqlClient 2.1 preview releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2020/10/23 | 2.1.0-preview2.20297.7 | [release notes](2.1.0-preview2.md) | -| 2020/08/21 | 2.1.0-preview1.20235.1 | [release notes](2.1.0-preview1.md) | diff --git a/release-notes/3.0/3.0.md b/release-notes/3.0/3.0.md deleted file mode 100644 index e35ae11698..0000000000 --- a/release-notes/3.0/3.0.md +++ /dev/null @@ -1,16 +0,0 @@ -# Microsoft.Data.SqlClient 3.0 Releases - -The following Microsoft.Data.SqlClient 3.0 stable releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2021/09/24 | 3.0.1 | [release notes](3.0.1.md) | -| 2021/06/09 | 3.0.0 | [release notes](3.0.0.md) | - -The following Microsoft.Data.SqlClient 3.0 preview releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2021/05/20 | 3.0.0-preview3.21140.5 | [release notes](3.0.0-preview3.md) | -| 2021/04/15 | 3.0.0-preview2.21106.5 | [release notes](3.0.0-preview2.md) | -| 2021/03/15 | 3.0.0-preview1.21075.2 | [release notes](3.0.0-preview1.md) | diff --git a/release-notes/3.1/3.1.2.md b/release-notes/3.1/3.1.2.md index 6f62706506..175681dcb5 100644 --- a/release-notes/3.1/3.1.2.md +++ b/release-notes/3.1/3.1.2.md @@ -17,7 +17,7 @@ This update brings the below changes over the previous release: ## Target Platform Support -- .NET Framework 4.6.1+ (Windows x86, Windows x64) +- .NET Framework 4.6.1+ (Windows ARM64, Windows x86, Windows x64) - .NET Core 2.1+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/3.1/3.1.3.md b/release-notes/3.1/3.1.3.md index 3b485fcd6c..43012e06d1 100644 --- a/release-notes/3.1/3.1.3.md +++ b/release-notes/3.1/3.1.3.md @@ -11,7 +11,7 @@ This update brings the below changes over the previous release: ## Target Platform Support -- .NET Framework 4.6.1+ (Windows x86, Windows x64) +- .NET Framework 4.6.1+ (Windows ARM64, Windows x86, Windows x64) - .NET Core 2.1+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/3.1/3.1.4.md b/release-notes/3.1/3.1.4.md index 712a873297..5180dfefdb 100644 --- a/release-notes/3.1/3.1.4.md +++ b/release-notes/3.1/3.1.4.md @@ -14,7 +14,7 @@ This update brings the below changes over the previous release: ## Target Platform Support -- .NET Framework 4.6.1+ (Windows x86, Windows x64) +- .NET Framework 4.6.1+ (Windows ARM64, Windows x86, Windows x64) - .NET Core 2.1+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/3.1/3.1.5.md b/release-notes/3.1/3.1.5.md index b11176ebe0..a60c8b42b4 100644 --- a/release-notes/3.1/3.1.5.md +++ b/release-notes/3.1/3.1.5.md @@ -11,7 +11,7 @@ This update brings the below changes over the previous release: ## Target Platform Support -- .NET Framework 4.6.1+ (Windows x86, Windows x64) +- .NET Framework 4.6.1+ (Windows ARM64, Windows x86, Windows x64) - .NET Core 2.1+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/3.1/3.1.7.md b/release-notes/3.1/3.1.7.md index b6a5db7132..57e2cd3100 100644 --- a/release-notes/3.1/3.1.7.md +++ b/release-notes/3.1/3.1.7.md @@ -17,7 +17,7 @@ This update brings the below changes over the previous release: ## Target Platform Support -- .NET Framework 4.6.1+ (Windows x86, Windows x64) +- .NET Framework 4.6.1+ (Windows ARM64, Windows x86, Windows x64) - .NET Core 2.1+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/3.1/3.1.md b/release-notes/3.1/3.1.md deleted file mode 100644 index af831bde67..0000000000 --- a/release-notes/3.1/3.1.md +++ /dev/null @@ -1,13 +0,0 @@ -# Microsoft.Data.SqlClient 3.1 Releases - -The following Microsoft.Data.SqlClient 3.1 stable releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2024/08/20 | 3.1.7 | [release notes](3.1.7.md) | -| 2024/01/09 | 3.1.5 | [release notes](3.1.5.md) | -| 2023/10/31 | 3.1.4 | [release notes](3.1.4.md) | -| 2023/03/10 | 3.1.3 | [release notes](3.1.3.md) | -| 2023/02/03 | 3.1.2 | [release notes](3.1.2.md) | -| 2022/08/12 | 3.1.1 | [release notes](3.1.1.md) | -| 2022/03/30 | 3.1.0 | [release notes](3.1.0.md) | diff --git a/release-notes/4.0/4.0.md b/release-notes/4.0/4.0.md deleted file mode 100644 index 98c70eb646..0000000000 --- a/release-notes/4.0/4.0.md +++ /dev/null @@ -1,21 +0,0 @@ -# Microsoft.Data.SqlClient 4.0 Releases - -The following Microsoft.Data.SqlClient 4.0 stable releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2024/08/21 | 4.0.6 | [release notes](4.0.6.md) | -| 2024/01/09 | 4.0.5 | [release notes](4.0.5.md) | -| 2023/10/30 | 4.0.4 | [release notes](4.0.4.md) | -| 2023/04/20 | 4.0.3 | [release notes](4.0.3.md) | -| 2022/09/13 | 4.0.2 | [release notes](4.0.2.md) | -| 2022/01/17 | 4.0.1 | [release notes](4.0.1.md) | -| 2021/11/18 | 4.0.0 | [release notes](4.0.0.md) | - -The following Microsoft.Data.SqlClient 4.0 preview releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2021/10/20 | 4.0.0-preview3.21293.2 | [release notes](4.0.0-preview3.md) | -| 2021/09/21 | 4.0.0-preview2.21264.2 | [release notes](4.0.0-preview2.md) | -| 2021/08/25 | 4.0.0-preview1.21237.2 | [release notes](4.0.0-preview1.md) | diff --git a/release-notes/4.1/4.1.md b/release-notes/4.1/4.1.md deleted file mode 100644 index a310caa5f7..0000000000 --- a/release-notes/4.1/4.1.md +++ /dev/null @@ -1,8 +0,0 @@ -# Microsoft.Data.SqlClient 4.1 Releases - -The following Microsoft.Data.SqlClient 4.1 stable releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2022/09/13 | 4.1.1 | [release notes](4.1.1.md) | -| 2022/01/31 | 4.1.0 | [release notes](4.1.0.md) | diff --git a/release-notes/5.0/5.0.md b/release-notes/5.0/5.0.md deleted file mode 100644 index 0f8d216061..0000000000 --- a/release-notes/5.0/5.0.md +++ /dev/null @@ -1,17 +0,0 @@ -# Microsoft.Data.SqlClient 5.0 Releases - -The following Microsoft.Data.SqlClient 5.0 stable releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2023/03/31 | 5.0.2 | [release notes](5.0.2.md) | -| 2022/10/07 | 5.0.1 | [release notes](5.0.1.md) | -| 2022/08/05 | 5.0.0 | [release notes](5.0.0.md) | - -The following Microsoft.Data.SqlClient 5.0 preview releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2022/06/16 | 5.0.0-preview3.22168.1 | [release notes](5.0.0-preview3.md) | -| 2022/04/06 | 5.0.0-preview2.22096.2 | [release notes](5.0.0-preview2.md) | -| 2022/03/09 | 5.0.0-preview1.22069.1 | [release notes](5.0.0-preview1.md) | diff --git a/release-notes/5.1/5.1.0-preview2.md b/release-notes/5.1/5.1.0-preview2.md index 9098c7b044..5bd4a240b0 100644 --- a/release-notes/5.1/5.1.0-preview2.md +++ b/release-notes/5.1/5.1.0-preview2.md @@ -42,7 +42,7 @@ The default value of the `ServerCertificate` connection setting is an empty stri ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.1/5.1.0.md b/release-notes/5.1/5.1.0.md index adbf449e63..2833f371f0 100644 --- a/release-notes/5.1/5.1.0.md +++ b/release-notes/5.1/5.1.0.md @@ -59,7 +59,7 @@ The default value of the `ServerCertificate` connection setting is an empty stri ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.1/5.1.1.md b/release-notes/5.1/5.1.1.md index 057466a565..fea3f68033 100644 --- a/release-notes/5.1/5.1.1.md +++ b/release-notes/5.1/5.1.1.md @@ -17,7 +17,7 @@ Thanks to the following public contributors. Their efforts toward this project a ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.1/5.1.2.md b/release-notes/5.1/5.1.2.md index cb9ccc5992..20a71f34ab 100644 --- a/release-notes/5.1/5.1.2.md +++ b/release-notes/5.1/5.1.2.md @@ -25,7 +25,7 @@ Thanks to the following public contributors. Their efforts toward this project a ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.1/5.1.3.md b/release-notes/5.1/5.1.3.md index 7889bf61bb..f96e347212 100644 --- a/release-notes/5.1/5.1.3.md +++ b/release-notes/5.1/5.1.3.md @@ -14,7 +14,7 @@ Thanks to the following public contributors. Their efforts toward this project a ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.1/5.1.4.md b/release-notes/5.1/5.1.4.md index c1638b1eee..62c325750e 100644 --- a/release-notes/5.1/5.1.4.md +++ b/release-notes/5.1/5.1.4.md @@ -17,7 +17,7 @@ Thanks to the following public contributors. Their efforts toward this project a ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.1/5.1.5.md b/release-notes/5.1/5.1.5.md index e05c26861f..540061f8f1 100644 --- a/release-notes/5.1/5.1.5.md +++ b/release-notes/5.1/5.1.5.md @@ -19,7 +19,7 @@ Thanks to the following public contributors. Their efforts toward this project a ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.1/5.1.6.md b/release-notes/5.1/5.1.6.md index a5404c84da..3c6cb9ebe6 100644 --- a/release-notes/5.1/5.1.6.md +++ b/release-notes/5.1/5.1.6.md @@ -22,7 +22,7 @@ Thanks to the following public contributors. Their efforts toward this project a ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.1/5.1.7.md b/release-notes/5.1/5.1.7.md new file mode 100644 index 0000000000..f8716c7b21 --- /dev/null +++ b/release-notes/5.1/5.1.7.md @@ -0,0 +1,76 @@ +# Release Notes + +## Microsoft.Data.SqlClient 5.1.7 - April 25, 2025 + +This update brings the following changes since the 5.1.6 release: + +### Fixed + +- Fixed possible `NullPointerException` during socket receive (PR [#3285](https://github.com/dotnet/SqlClient/pull/3285)) +- Fixed inconsistencies between source and reference projects (PR [#3180](https://github.com/dotnet/SqlClient/pull/3180)) + +### Changed + +- Updated the following dependencies: + - [Microsoft.Data.SqlClient.SNI](https://www.nuget.org/packages/Microsoft.Data.SqlClient.SNI/5.1.2) 5.1.1 to 5.1.2 for .NET Framework on Windows (PR [#3294](https://github.com/dotnet/SqlClient/pull/3294)) + - [Microsoft.Data.SqlClient.SNI.runtime](https://www.nuget.org/packages/Microsoft.Data.SqlClient.SNI.runtime/5.1.2) 5.1.1 to 5.1.2 for .NET on Windows (PR [#3294](https://github.com/dotnet/SqlClient/pull/3294)) + - [Microsoft.Extensions.Caching.Memory](https://www.nuget.org/packages/Microsoft.Extensions.Caching.Memory/6.0.3) 6.0.1 to 6.0.3 - Avoid [CVE-2024-43483](https://github.com/advisories/GHSA-qj66-m88j-hmgj) (PR [#3068](https://github.com/dotnet/SqlClient/pull/3068)) + - [Microsoft.Extensions.Hosting](https://www.nuget.org/packages/Microsoft.Extensions.Hosting/6.0.1) 6.0.0 to 6.0.1 - Avoid transitive dependency on vulnerable [System.Text.Json](https://www.nuget.org/packages/System.Text.Json/6.0.0) 6.0.0 (PR [#3207](https://github.com/dotnet/SqlClient/pull/3207)) + - [System.Private.Uri](https://www.nuget.org/packages/System.Private.Uri) 4.3.2 - Avoid transitive [CVE-2019-0820](https://msrc.microsoft.com/update-guide/en-US/advisory/CVE-2019-0820) (PR [#3077](https://github.com/dotnet/SqlClient/pull/3077)) + - [System.Text.Encodings.Web](https://www.nuget.org/packages/System.Text.Encodings.Web/6.0.1) 6.0.0 to 6.0.1 - Avoid transitive downgrade for .NET Framework targets (PR [#3279](https://github.com/dotnet/SqlClient/pull/3279)) + - [System.Text.Json](https://www.nuget.org/packages/System.Text.Json/6.0.11) 6.0.11 - Avoid transitive dependencies on older vulnerable versions for .NET Framework targets (PR [#3279](https://github.com/dotnet/SqlClient/pull/3279)) + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) +- .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) + +## Dependencies + +### .NET Framework + +- Azure.Identity 1.11.4 +- Microsoft.Data.SqlClient.SNI 5.1.2 +- Microsoft.Identity.Client 4.61.3 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.InteropServices.RuntimeInformation 4.3.0 +- System.Text.Encoding.Web 6.0.1 +- System.Text.Json 6.0.11 + +### .NET + +- Azure.Identity 1.11.4 +- Microsoft.Data.SqlClient.SNI 5.1.2 +- Microsoft.Identity.Client 4.61.3 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Diagnostics.DiagnosticSource 6.0.1 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.1 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 + +### .NET Standard + +- Azure.Identity 1.11.4 +- Microsoft.Data.SqlClient.SNI 5.1.2 +- Microsoft.Identity.Client 4.61.3 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- Microsoft.SqlServer.Server 1.0.0 +- Microsoft.Win32.Registry 5.0.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.1 +- System.Runtime.Loader 4.3.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 diff --git a/release-notes/5.1/5.1.md b/release-notes/5.1/5.1.md deleted file mode 100644 index 99db6d4c58..0000000000 --- a/release-notes/5.1/5.1.md +++ /dev/null @@ -1,20 +0,0 @@ -# Microsoft.Data.SqlClient 5.1 Releases - -The following Microsoft.Data.SqlClient 5.1 stable releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2024/08/27 | 5.1.6 | [release notes](5.1.6.md) | -| 2024/01/29 | 5.1.5 | [release notes](5.1.5.md) | -| 2024/01/09 | 5.1.4 | [release notes](5.1.4.md) | -| 2024/01/09 | 5.1.3 | [release notes](5.1.3.md) | -| 2023/10/26 | 5.1.2 | [release notes](5.1.2.md) | -| 2023/03/28 | 5.1.1 | [release notes](5.1.1.md) | -| 2023/01/19 | 5.1.0 | [release notes](5.1.0.md) | - -The following Microsoft.Data.SqlClient 5.1 preview releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2022/11/10 | 5.1.0-preview2.22314.2 | [release notes](5.1.0-preview2.md) | -| 2022/10/19 | 5.1.0-preview1.22279.3 | [release notes](5.1.0-preview1.md) | diff --git a/release-notes/5.1/README.md b/release-notes/5.1/README.md index 99db6d4c58..27ed889b2a 100644 --- a/release-notes/5.1/README.md +++ b/release-notes/5.1/README.md @@ -4,6 +4,7 @@ The following Microsoft.Data.SqlClient 5.1 stable releases have been shipped: | Release Date | Version | Notes | | :-- | :-- | :--: | +| 2025/04/25 | 5.1.7 | [release notes](5.1.7.md) | | 2024/08/27 | 5.1.6 | [release notes](5.1.6.md) | | 2024/01/29 | 5.1.5 | [release notes](5.1.5.md) | | 2024/01/09 | 5.1.4 | [release notes](5.1.4.md) | diff --git a/release-notes/5.2/5.2.0-preview1.md b/release-notes/5.2/5.2.0-preview1.md index 152b3169b8..e937f6b74c 100644 --- a/release-notes/5.2/5.2.0-preview1.md +++ b/release-notes/5.2/5.2.0-preview1.md @@ -40,7 +40,7 @@ Thanks to the following public contributors. Their efforts toward this project a ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.2/5.2.0-preview2.md b/release-notes/5.2/5.2.0-preview2.md index 360894b93a..c7523841cd 100644 --- a/release-notes/5.2/5.2.0-preview2.md +++ b/release-notes/5.2/5.2.0-preview2.md @@ -61,7 +61,7 @@ Example usage: ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.2/5.2.0-preview3.md b/release-notes/5.2/5.2.0-preview3.md index 82173a5744..5e9448a50b 100644 --- a/release-notes/5.2/5.2.0-preview3.md +++ b/release-notes/5.2/5.2.0-preview3.md @@ -58,7 +58,7 @@ Example usage: ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.2/5.2.0-preview4.md b/release-notes/5.2/5.2.0-preview4.md index eb94c0c73f..8c48f33ae9 100644 --- a/release-notes/5.2/5.2.0-preview4.md +++ b/release-notes/5.2/5.2.0-preview4.md @@ -93,7 +93,7 @@ class Program ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.2/5.2.0-preview5.md b/release-notes/5.2/5.2.0-preview5.md index 87de1c81ea..094d1e4989 100644 --- a/release-notes/5.2/5.2.0-preview5.md +++ b/release-notes/5.2/5.2.0-preview5.md @@ -27,7 +27,7 @@ Thanks to the following public contributors. Their efforts toward this project a ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.2/5.2.0.md b/release-notes/5.2/5.2.0.md index b431e7c675..8ade56aef2 100644 --- a/release-notes/5.2/5.2.0.md +++ b/release-notes/5.2/5.2.0.md @@ -196,7 +196,7 @@ Example usage: ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.2/5.2.1.md b/release-notes/5.2/5.2.1.md index a64a4f4589..ba72800828 100644 --- a/release-notes/5.2/5.2.1.md +++ b/release-notes/5.2/5.2.1.md @@ -20,7 +20,7 @@ This update brings the below changes over the previous release: ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.2/5.2.2.md b/release-notes/5.2/5.2.2.md index 1947d5f373..57ec61ac9e 100644 --- a/release-notes/5.2/5.2.2.md +++ b/release-notes/5.2/5.2.2.md @@ -21,7 +21,7 @@ This update brings the below changes over the previous release: ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) - .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) diff --git a/release-notes/5.2/5.2.3.md b/release-notes/5.2/5.2.3.md new file mode 100644 index 0000000000..fe9bb0077c --- /dev/null +++ b/release-notes/5.2/5.2.3.md @@ -0,0 +1,97 @@ +# Release Notes + +## [Stable release 5.2.3] - 2025-04-29 + +This update brings the following changes since the 5.2.2 release: + +### Fixed + +- Fixed possible `NullPointerException` during socket receive (PR [#3284](https://github.com/dotnet/SqlClient/pull/3284)) +- Fixed inconsistencies between source and reference projects (PR [#3124](https://github.com/dotnet/SqlClient/pull/3124)) +- Adjusted retry logic to allow errors with negative numbers to be considered transient (PR [#3185](https://github.com/dotnet/SqlClient/pull/3185)) + +### Changed + +- Updated the following dependencies: + - [System.Private.Uri](https://www.nuget.org/packages/System.Private.Uri) 4.3.2 - Avoid transitive [CVE-2019-0820](https://msrc.microsoft.com/update-guide/en-US/advisory/CVE-2019-0820) (PR [#3076](https://github.com/dotnet/SqlClient/pull/3076)) + - [Microsoft.Extensions.Caching.Memory](https://www.nuget.org/packages/Microsoft.Extensions.Caching.Memory/6.0.3) 6.0.1 to 6.0.3 - Avoid [CVE-2024-43483](https://github.com/advisories/GHSA-qj66-m88j-hmgj) (PR [#3280](https://github.com/dotnet/SqlClient/pull/3280)) + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) +- .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) +- .NET Standard 2.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) + +### Dependencies + +#### .NET Framework + +- Microsoft.Data.SqlClient.SNI 5.2.0 +- Azure.Identity 1.11.4 +- Microsoft.Identity.Client 4.61.3 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.InteropServices.RuntimeInformation 4.3.0 +- System.Text.Encodings.Web 6.0.0 + +#### .NET 6 + +- Microsoft.Data.SqlClient.SNI.runtime 5.2.0 +- Azure.Identity 1.11.4 +- Microsoft.Identity.Client 4.61.3 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Runtime.Caching 6.0.0 + +#### .NET 8 + +- Microsoft.Data.SqlClient.SNI.runtime 5.2.0 +- Azure.Identity 1.11.4 +- Microsoft.Identity.Client 4.61.3 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 8.0.0 +- System.Runtime.Caching 8.0.0 + +#### .NET Standard 2.0 + +- Microsoft.Data.SqlClient.SNI.runtime 5.2.0 +- Azure.Identity 1.11.4 +- Microsoft.Identity.Client 4.61.3 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- Microsoft.SqlServer.Server 1.0.0 +- Microsoft.Win32.Registry 5.0.0 +- System.Buffers 4.5.1 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Diagnostics.DiagnosticSource 6.0.1 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.0 +- System.Runtime.Loader 4.3.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 + +#### .NET Standard 2.1 + +- Microsoft.Data.SqlClient.SNI.runtime 5.2.0 +- Azure.Identity 1.11.4 +- Microsoft.Identity.Client 4.61.3 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 6.35.0 +- Microsoft.IdentityModel.JsonWebTokens 6.35.0 +- Microsoft.SqlServer.Server 1.0.0 +- Microsoft.Win32.Registry 5.0.0 +- System.Configuration.ConfigurationManager 6.0.1 +- System.Diagnostics.DiagnosticSource 6.0.1 +- System.Runtime.Caching 6.0.0 +- System.Text.Encoding.CodePages 6.0.0 +- System.Text.Encodings.Web 6.0.0 +- System.Runtime.Loader 4.3.0 +- System.Security.Cryptography.Cng 5.0.0 +- System.Security.Principal.Windows 5.0.0 + diff --git a/release-notes/5.2/5.2.md b/release-notes/5.2/5.2.md deleted file mode 100644 index e94144af62..0000000000 --- a/release-notes/5.2/5.2.md +++ /dev/null @@ -1,20 +0,0 @@ -# Microsoft.Data.SqlClient 5.2 Releases - -The following Microsoft.Data.SqlClient 5.2 stable releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2024/08/27 | 5.2.2 | [release notes](5.2.2.md) | -| 2024/05/31 | 5.2.1 | [release notes](5.2.1.md) | -| 2024/02/28 | 5.2.0 | [release notes](5.2.0.md) | - -The following Microsoft.Data.SqlClient 5.2 preview releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2024/01/24 | 5.2.0-preview5.24024.3 | [release notes](5.2.0-preview5.md) | -| 2023/12/08 | 5.2.0-preview4.23342.2 | [release notes](5.2.0-preview4.md) | -| 2023/07/20 | 5.2.0-preview3.23201.1 | [release notes](5.2.0-preview3.md) | -| 2023/06/08 | 5.2.0-preview2.23159.1 | [release notes](5.2.0-preview2.md) | -| 2023/04/20 | 5.2.0-preview1.23109.1 | [release notes](5.2.0-preview1.md) | - diff --git a/release-notes/5.2/README.md b/release-notes/5.2/README.md index ed6f4a377a..c636d07d00 100644 --- a/release-notes/5.2/README.md +++ b/release-notes/5.2/README.md @@ -4,6 +4,7 @@ The following Microsoft.Data.SqlClient 5.2 stable releases have been shipped: | Release Date | Version | Notes | | :-- | :-- | :--: | +| 2025/04/29 | 5.2.3 | [release notes](5.2.3.md) | | 2024/08/27 | 5.2.2 | [release notes](5.2.2.md) | | 2024/05/31 | 5.2.1 | [release notes](5.2.1.md) | | 2024/02/28 | 5.2.0 | [release notes](5.2.0.md) | diff --git a/release-notes/6.0/6.0.0-preview1.md b/release-notes/6.0/6.0.0-preview1.md index 86589346d8..96efc13638 100644 --- a/release-notes/6.0/6.0.0-preview1.md +++ b/release-notes/6.0/6.0.0-preview1.md @@ -39,7 +39,7 @@ Thanks to the following public contributors. Their efforts toward this project a - Fixed clone of `SqlConnection` to include `AccessTokenCallback`. [#2525](https://github.com/dotnet/SqlClient/pull/2525) - Fixed issue with `DateTimeOffset` in table-valued parameters, which was introduced in 5.2. [#2453](https://github.com/dotnet/SqlClient/pull/2453) - Fixed `ArgumentNullException` on `SqlDataRecord.GetValue` when using user-defined data type on .NET. [#2448](https://github.com/dotnet/SqlClient/pull/2448) -- Fixed `SqlBuffer` and `SqlGuild` when it's null. [#2310](https://github.com/dotnet/SqlClient/pull/2310) +- Fixed `SqlBuffer` and `SqlGuid` when it's null. [#2310](https://github.com/dotnet/SqlClient/pull/2310) - Fixed `SqlBulkCopy.WriteToServer` state in a consecutive calls. [#2375](https://github.com/dotnet/SqlClient/pull/2375) - Fixed null reference exception with `SqlConnection.FireInfoMessageEventOnUserErrors` after introducing the batch command. [#2399](https://github.com/dotnet/SqlClient/pull/2399) @@ -64,7 +64,7 @@ Thanks to the following public contributors. Their efforts toward this project a ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) ### Dependencies diff --git a/release-notes/6.0/6.0.0-preview2.md b/release-notes/6.0/6.0.0-preview2.md index 6a6d7054ca..c6c78c5b64 100644 --- a/release-notes/6.0/6.0.0-preview2.md +++ b/release-notes/6.0/6.0.0-preview2.md @@ -37,7 +37,7 @@ Thanks to the following public contributors. Their efforts toward this project a ## Target Platform Support -- .NET Framework 4.6.2+ (Windows x86, Windows x64) +- .NET Framework 4.6.2+ (Windows ARM64, Windows x86, Windows x64) - .NET 6.0+ (Windows x86, Windows x64, Windows ARM64, Windows ARM, Linux, macOS) ### Dependencies diff --git a/release-notes/6.0/6.0.0-preview3.md b/release-notes/6.0/6.0.0-preview3.md new file mode 100644 index 0000000000..5f8b774268 --- /dev/null +++ b/release-notes/6.0/6.0.0-preview3.md @@ -0,0 +1,83 @@ +# Release Notes + +## [Preview Release 6.0.0-preview3.24332.3] - 2024-11-27 + +This update brings the below changes over the previous release: + +### Contributors + +Thanks to the following public contributors. Their efforts toward this project are very much appreciated. + +- [MichelZ](https://github.com/MichelZ) +- [EamonHetherton](https://github.com/EamonHetherton) +- [edwardneal](https://github.com/edwardneal) +- [deadlydog](https://github.com/deadlydog) +- [mus65](https://github.com/mus65) + +## Breaking Changes +- Dropped support for .NET 6 [#2927](https://github.com/dotnet/SqlClient/pull/2927) +- Removed SQL 2000 client-side debugging support for .NET Framework [#2981](https://github.com/dotnet/SqlClient/pull/2981), [#2940](https://github.com/dotnet/SqlClient/pull/2940) + +### Added +- Enabled NuGet package auditing via NuGet.org audit source [#3024](https://github.com/dotnet/SqlClient/pull/3024) +- Added support for .NET 9 [#2946](https://github.com/dotnet/SqlClient/pull/2946) +- Added dependency on System.Security.Cryptography.Pkcs:9.0.0 to address [SYSLIB0057](https://learn.microsoft.com/en-us/dotnet/fundamentals/syslib-diagnostics/syslib0057)[#2946](https://github.com/dotnet/SqlClient/pull/2946) +- Added dependency on Microsoft.Bcl.Cryptography:9.0.0 [#2946](https://github.com/dotnet/SqlClient/pull/2946) +- Added missing SqlCommand_BeginExecuteReader code sample [#3009](https://github.com/dotnet/SqlClient/pull/3009) +- Added support for SqlConnectionOverrides in OpenAsync() API [#2433](https://github.com/dotnet/SqlClient/pull/2433) +- Added localization in Czech, Polish, and Turkish [#2987](https://github.com/dotnet/SqlClient/pull/2987) + +### Fixed +- Reverted default value of UseMinimumLoginTimeout context switch to 'true' [#2419](https://github.com/dotnet/SqlClient/pull/2419) +- Added missing DynamicallyAccessedMembers attributes in .NET Runtime reference assemblies. [#2946](https://github.com/dotnet/SqlClient/pull/2946) +- Synchronized dependencies of Reference Assemblies with Runtime assemblies [#2878](https://github.com/dotnet/SqlClient/pull/2878) +- Fixed lazy initialization of the _SqlMetaData hidden column map for .NET Framework [#2964](https://github.com/dotnet/SqlClient/pull/2964) + +### Changed +- Updated Microsoft.Extensions.Caching.Memory to 9.0.0 for all frameworks [#2946](https://github.com/dotnet/SqlClient/pull/2946) +- Updated System.Configuration.ConfigurationManager to 9.0.0 [#2946](https://github.com/dotnet/SqlClient/pull/2946) +- Updated docs to use absolute links [#2949](https://github.com/dotnet/SqlClient/pull/2949) +- Removed System.Text.Json dependency from .NET 8 [#2930](https://github.com/dotnet/SqlClient/pull/2930) + +## 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 Framework + +- Azure.Identity 1.11.4 +- Microsoft.Bcl.Cryptography 9.0.0 +- Microsoft.Data.SqlClient.SNI 6.0.0-preview1.24226.4 +- Microsoft.Extensions.Caching.Memory 9.0.0 +- Microsoft.IdentityModel.JsonWebTokens 7.5.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.5.0 +- System.Buffers 4.5.1 +- System.Security.Cryptography.Pkcs 9.0.0 +- System.Text.Encodings.Web 6.0.0 +- System.Text.Json 6.0.10 + +#### .NET 8 + +- Azure.Identity 1.11.4 +- Microsoft.Bcl.Cryptography 9.0.0 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.0-preview1.24226.4 +- Microsoft.Extensions.Caching.Memory 9.0.0 +- Microsoft.IdentityModel.JsonWebTokens 7.5.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.5.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 9.0.0 +- System.Security.Cryptography.Pkcs 9.0.0 + +#### .NET 9 + +- Azure.Identity 1.11.4 +- Microsoft.Bcl.Cryptography 9.0.0 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.0-preview1.24226.4 +- Microsoft.Extensions.Caching.Memory 9.0.0 +- Microsoft.IdentityModel.JsonWebTokens 7.5.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.5.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 9.0.0 +- System.Security.Cryptography.Pkcs 9.0.0 diff --git a/release-notes/6.0/6.0.0.md b/release-notes/6.0/6.0.0.md new file mode 100644 index 0000000000..37d6b48cd2 --- /dev/null +++ b/release-notes/6.0/6.0.0.md @@ -0,0 +1,237 @@ +# Release Notes + +## [Stable release 6.0.0] - 2024-12-09 + +This update brings the below changes over the previous stable release: + +### Breaking Changes + +- Removed support for .NET Standard. [#2386](https://github.com/dotnet/SqlClient/pull/2386) +- Removed support for .NET 6 [#2927](https://github.com/dotnet/SqlClient/pull/2927) +- Removed UWP (UAP) references. [#2483](https://github.com/dotnet/SqlClient/pull/2483) +- Removed SQL 2000 client-side debugging support for .NET Framework [#2981](https://github.com/dotnet/SqlClient/pull/2981), [#2940](https://github.com/dotnet/SqlClient/pull/2940) + +### Added + +#### JSON Support + +JSON data type support is now available in Microsoft.Data.SqlClient v6.0. This release introduces `SqlJson` type available as an extension to `System.Data.SqlDbTypes`: + +```csharp +using System; +using System.Data.SqlTypes; +using System.Text.Json; + +namespace Microsoft.Data.SqlTypes +{ + /// + /// Represents the JSON data type in SQL Server. + /// + public class SqlJson : INullable + { + /// + /// Parameterless constructor. Initializes a new instance of the SqlJson class which + /// represents a null JSON value. + /// + public SqlJson() { } + + /// + /// Takes a as input and initializes a new instance of the SqlJson class. + /// + /// + public SqlJson(string jsonString) { } + + /// + /// Takes a as input and initializes a new instance of the SqlJson class. + /// + /// + public SqlJson(JsonDocument jsonDoc) { } + + /// + public bool IsNull => throw null; + + /// + /// Represents a null instance of the type. + /// + public static SqlJson Null { get { throw null; } } + + /// + /// Gets the string representation of the Json content of this instance. + /// + public string Value { get ; } + } +} +``` + +The JSON data type supports reading, writing, streaming, and performing bulk copy operations. + +Implemented in PRs: [#2916](https://github.com/dotnet/SqlClient/pull/2916), [#2892](https://github.com/dotnet/SqlClient/pull/2892), [#2891](https://github.com/dotnet/SqlClient/pull/2891), [#2880](https://github.com/dotnet/SqlClient/pull/2880), [#2882](https://github.com/dotnet/SqlClient/pull/2882), [#2829](https://github.com/dotnet/SqlClient/pull/2829), [#2830](https://github.com/dotnet/SqlClient/pull/2830) + +#### Introducing SqlClientDiagnostics + +`SqlClientDiagnostic` is now available as a strongly-typed collection of key-value pairs that can be captured by consuming applications. + +```csharp +// Class that provides strongly-typed collection of key-value pairs for SqlClient diagnostic objects. +public abstract class SqlClientDiagnostic : System.Collections.Generic.IReadOnlyList> +{ + // A guid value used to correlate before, after and error events. + public System.Guid OperationId; + + // The name of the operation. + public string Operation; + + // The timestamp of the event. + public long Timestamp; + + // The number of elements in the collection. + public int Count; + + // The element at the specified index in the read-only list. + public System.Collections.Generic.KeyValuePair this[int index]; + + // An enumerator that can be used to iterate through the collection. + public System.Collections.Generic.IEnumerator> GetEnumerator(); +} +``` + +Implemented in PR: [#2226](https://github.com/dotnet/SqlClient/pull/2226) + +#### Added Support for Connection Overrides in `OpenAsync()` API + +The default behavior of `SqlConnection.OpenAsync()` can be overridden to disable the ten-second delay and automatic connection retries triggered by transient errors. + +```csharp +using(SqlConnection sqlConnection = new SqlConnection("Data Source=(local);Integrated Security=true;Initial Catalog=AdventureWorks;")) +{ + await sqlConnection.OpenAsync(SqlConnectionOverrides.OpenWithoutRetry, cancellationToken); +} +``` + +Implemented in PR: [#2433](https://github.com/dotnet/SqlClient/pull/2433) + +#### Other Additions + +- Added support for .NET 9 [#2946](https://github.com/dotnet/SqlClient/pull/2946) +- Added localization in Czech, Polish, and Turkish [#2987](https://github.com/dotnet/SqlClient/pull/2987) +- Added `TokenCredential` object to take advantage of token caching in `ActiveDirectoryAuthenticationProvider`. [#2380](https://github.com/dotnet/SqlClient/pull/2380) +- Added support for using `DateOnly` and `TimeOnly` in `DataTable` and `SqlDataRecord` structured parameters. [#2258](https://github.com/dotnet/SqlClient/pull/2258) +- Added scope trace for `GenerateSspiClientContext`. [#2497](https://github.com/dotnet/SqlClient/pull/2497), [#2725](https://github.com/dotnet/SqlClient/pull/2725) +- Added readme to NuGet package [#2826](https://github.com/dotnet/SqlClient/pull/2826) +- Enabled NuGet package auditing via NuGet.org audit source [#3024](https://github.com/dotnet/SqlClient/pull/3024) +- Added missing SqlCommand_BeginExecuteReader code sample [#3009](https://github.com/dotnet/SqlClient/pull/3009) + +### Fixed + +- Fixed `Socket.Connect` timeout issue caused by thread starvation. [#2777](https://github.com/dotnet/SqlClient/pull/2777) +- Fixed pending data with `SqlDataReader` against an encrypted column. [#2618](https://github.com/dotnet/SqlClient/pull/2618) +- Fixed Entra authentication when using infinite connection timeout in `ActiveDirectoryAuthenticationProvider`. [#2651](https://github.com/dotnet/SqlClient/pull/2651) +- Fixed `GetSchema` by excluding unsupported engines due to lack of support for `ASSEMBLYPROPERTY` function. [#2593](https://github.com/dotnet/SqlClient/pull/2593) +- Fixed SSPI retry negotiation with default port in .NET. [#2559](https://github.com/dotnet/SqlClient/pull/2559) +- Fixed assembly path in .NET 8.0 and `.AssemblyAttributes`. [#2550](https://github.com/dotnet/SqlClient/pull/2550) +- Fixed certificate chain validation. [#2487](https://github.com/dotnet/SqlClient/pull/2487) +- Fixed clone of `SqlConnection` to include `AccessTokenCallback`. [#2525](https://github.com/dotnet/SqlClient/pull/2525) +- Fixed issue with `DateTimeOffset` in table-valued parameters, which was introduced in 5.2. [#2453](https://github.com/dotnet/SqlClient/pull/2453) +- Fixed `ArgumentNullException` on `SqlDataRecord.GetValue` when using user-defined data type on .NET. [#2448](https://github.com/dotnet/SqlClient/pull/2448) +- Fixed `SqlBuffer` and `SqlGuid` when it's null. [#2310](https://github.com/dotnet/SqlClient/pull/2310) +- Fixed `SqlBulkCopy.WriteToServer` state in a consecutive calls. [#2375](https://github.com/dotnet/SqlClient/pull/2375) +- Fixed null reference exception with `SqlConnection.FireInfoMessageEventOnUserErrors` after introducing the batch command. [#2399](https://github.com/dotnet/SqlClient/pull/2399) +- Fixed scale serialization when explicitly set to 0 [#2411](https://github.com/dotnet/SqlClient/pull/2411) +- Fixed issue blocking `GetSchema` commands from being enrolled into the current transaction [#2876](https://github.com/dotnet/SqlClient/pull/2876) +- Adjusted retry logic to allow errors with negative numbers to be considered transient [#2896](https://github.com/dotnet/SqlClient/pull/2896) +- Fixed string formatting in `OutOfMemory` exceptions [#2797](https://github.com/dotnet/SqlClient/pull/2797) +- Increased routing attempts to 10 in netcore for `LoginNoFailover` and added routing support to `LoginWithFailover` to standardize routing behavior between netcore and netfx [#2873](https://github.com/dotnet/SqlClient/pull/2873) +- Restructured documentation into XML format so that it displays correctly in Visual Studio [#2836](https://github.com/dotnet/SqlClient/pull/2836), [#2822](https://github.com/dotnet/SqlClient/pull/2822), [#2834](https://github.com/dotnet/SqlClient/pull/2834), [#2851](https://github.com/dotnet/SqlClient/pull/2851), [#2863](https://github.com/dotnet/SqlClient/pull/2863), [#2864](https://github.com/dotnet/SqlClient/pull/2864), [#2865](https://github.com/dotnet/SqlClient/pull/2865), [#2869](https://github.com/dotnet/SqlClient/pull/2869), [#2871](https://github.com/dotnet/SqlClient/pull/2871), [#2837](https://github.com/dotnet/SqlClient/pull/2837), [#2821](https://github.com/dotnet/SqlClient/pull/2821) +- Fixed cleanup behavior when column decryption fails. Prevents leaving stale data on the wire for pooled connections [#2843](https://github.com/dotnet/SqlClient/pull/2843), [#2825](https://github.com/dotnet/SqlClient/pull/2825) +- Reverted default value of `UseMinimumLoginTimeout` context switch to 'true' [#2419](https://github.com/dotnet/SqlClient/pull/2419) +- Added missing `DynamicallyAccessedMembers` attributes in .NET Runtime reference assemblies. [#2946](https://github.com/dotnet/SqlClient/pull/2946) +- Synchronized dependencies of reference assemblies with runtime assemblies [#2878](https://github.com/dotnet/SqlClient/pull/2878) +- Fixed lazy initialization of the `_SqlMetaData` hidden column map for .NET Framework [#2964](https://github.com/dotnet/SqlClient/pull/2964) + +### Changed + +- Dependency changes + - Added dependency on `Microsoft.Bcl.Cryptography:9.0.0` [#2946](https://github.com/dotnet/SqlClient/pull/2946) + - Added dependency on `System.Security.Cryptography.Pkcs:9.0.0` to address [SYSLIB0057](https://learn.microsoft.com/en-us/dotnet/fundamentals/syslib-diagnostics/syslib0057)[#2946](https://github.com/dotnet/SqlClient/pull/2946) + - Added dependency on `System.Text.Json` `8.0.5` for .NET 8+ and `6.0.10` for other versions [#2921](https://github.com/dotnet/SqlClient/pull/2921) + - Removed dependency on `Microsoft.Extensions.Caching.Memory` and `System.Security.Cryptography.Cng`, which were unnecessary after removing .NET Standard. [#2577](https://github.com/dotnet/SqlClient/pull/2577) + - Removed dependency on `System.Text.Json` from .NET 8+ [#2930](https://github.com/dotnet/SqlClient/pull/2930) + - Removed direct dependency to `Microsoft.Identity.Client` to take the transient dependecy through `Azure.Identity`. [#2577](https://github.com/dotnet/SqlClient/pull/2577) + - Replaced `System.Runtime.Caching` with `Microsoft.Extensions.Caching.Memory`. [#2493](https://github.com/dotnet/SqlClient/pull/2493) + - Updated `Azure.Core` version from `1.35.0` to `1.38.0`. [#2462](https://github.com/dotnet/SqlClient/pull/2462) + - Updated `Azure.Identity` version from `1.10.3` to `1.11.4`. [#2577](https://github.com/dotnet/SqlClient/pull/2577) + - Updated `Azure.Security.KeyVault.Keys` version from `4.4.0` to `4.5.0`. [#2462](https://github.com/dotnet/SqlClient/pull/2462) + - Updated `Microsoft.Data.SqlClient.SNI` version to `6.0.1`. [#3100](https://github.com/dotnet/SqlClient/pull/3096) + - Updated `Microsoft.IdentityModel.JsonWebTokens` and `Microsoft.IdentityModel.Protocols.OpenIdConnect` from `6.35.0` to `7.5.0`. [#2429](https://github.com/dotnet/SqlClient/pull/2429) + - Updated `Microsoft.Extensions.Caching.Memory` from `8.0.0` to `9.0.0` [#2921](https://github.com/dotnet/SqlClient/pull/2921) + - Updated `System.Configuration.ConfigurationManager` from `8.0.0` to `9.0.10` for .NET 8 [#2921](https://github.com/dotnet/SqlClient/pull/2921) +- Improved access to `SqlAuthenticationProviderManager.Instance` and avoid early object initiation. [#2636](https://github.com/dotnet/SqlClient/pull/2636) +- Removed undocumented properties of `Azure.Identity` in `ActiveDirectoryAuthenticationProvider`. [#2562](https://github.com/dotnet/SqlClient/pull/2562) +- Updated `EnableOptimizedParameterBinding` to only accept text mode commands. [#2417](https://github.com/dotnet/SqlClient/pull/2417) +- Improved memory allocation when reader opened by `CommandBehavior.SequentialAccess` over the big string columns. [#2356](https://github.com/dotnet/SqlClient/pull/2356) +- Improved SSPI by consolidating the context generation to single abstraction and using memory/span for SSPI generation. [#2255](https://github.com/dotnet/SqlClient/pull/2255), [#2447](https://github.com/dotnet/SqlClient/pull/2447) +- Reverted the [#2281](https://github.com/dotnet/SqlClient/pull/2281) code changes on ManagedSNI. [#2395](https://github.com/dotnet/SqlClient/pull/2395) +- Updated assembly version to 6.0.0.0. [#2382](https://github.com/dotnet/SqlClient/pull/2382), since Preview 1 +- Updated docs to use absolute links [#2949](https://github.com/dotnet/SqlClient/pull/2949), since Preview 3 +- Code health improvements: [#2366](https://github.com/dotnet/SqlClient/pull/2366), [#2369](https://github.com/dotnet/SqlClient/pull/2369), [#2376](https://github.com/dotnet/SqlClient/pull/2376), [#2381](https://github.com/dotnet/SqlClient/pull/2381), [#2390](https://github.com/dotnet/SqlClient/pull/2390), [#2392](https://github.com/dotnet/SqlClient/pull/2392), [#2403](https://github.com/dotnet/SqlClient/pull/2403), [#2410](https://github.com/dotnet/SqlClient/pull/2410), [#2413](https://github.com/dotnet/SqlClient/pull/2413), [#2425](https://github.com/dotnet/SqlClient/pull/2425), [#2428](https://github.com/dotnet/SqlClient/pull/2428), [#2440](https://github.com/dotnet/SqlClient/pull/2440), [#2442](https://github.com/dotnet/SqlClient/pull/2442), [#2443](https://github.com/dotnet/SqlClient/pull/2443), [#2450](https://github.com/dotnet/SqlClient/pull/2450), [#2466](https://github.com/dotnet/SqlClient/pull/2466), [#2486](https://github.com/dotnet/SqlClient/pull/2486), [#2521](https://github.com/dotnet/SqlClient/pull/2521), [#2522](https://github.com/dotnet/SqlClient/pull/2522), [#2533](https://github.com/dotnet/SqlClient/pull/2533), [#2552](https://github.com/dotnet/SqlClient/pull/2552), [#2560](https://github.com/dotnet/SqlClient/pull/2560), [#2726](https://github.com/dotnet/SqlClient/pull/2726), [#2751](https://github.com/dotnet/SqlClient/pull/2751), [#2805](https://github.com/dotnet/SqlClient/pull/2805), [#2811](https://github.com/dotnet/SqlClient/pull/2811), [#2812](https://github.com/dotnet/SqlClient/pull/2812), [#2814](https://github.com/dotnet/SqlClient/pull/2814), [#2820](https://github.com/dotnet/SqlClient/pull/2820), [#2831](https://github.com/dotnet/SqlClient/pull/2831), [#2835](https://github.com/dotnet/SqlClient/pull/2835), [#2844](https://github.com/dotnet/SqlClient/pull/2844), [#2854](https://github.com/dotnet/SqlClient/pull/2854), [#2885](https://github.com/dotnet/SqlClient/pull/2885), [#2889](https://github.com/dotnet/SqlClient/pull/2889), [#2897](https://github.com/dotnet/SqlClient/pull/2897), [#2898](https://github.com/dotnet/SqlClient/pull/2898), [#2907](https://github.com/dotnet/SqlClient/pull/2907), [#2910](https://github.com/dotnet/SqlClient/pull/2910), [#2915](https://github.com/dotnet/SqlClient/pull/2915), [#2928](https://github.com/dotnet/SqlClient/pull/2928), [#2929](https://github.com/dotnet/SqlClient/pull/2929), [#2936](https://github.com/dotnet/SqlClient/pull/2936), [#2939](https://github.com/dotnet/SqlClient/pull/2939) + +## Target Platform Support +- .NET Framework 4.6.2+ (Windows ARM64, Windows x64, Windows x86) +- .NET 8.0+ (Linux, macOS, Windows ARM64, Windows x64, Windows x86) + +### Dependencies + +#### .NET Framework + +- Azure.Identity 1.11.4 +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.SNI 6.0.1 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 7.5.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.5.0 +- System.Buffers 4.5.1 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Encodings.Web 6.0.0 +- System.Text.Json 6.0.10 +- System.Data.Common 4.3.0 + +#### .NET 8 + +- Azure.Identity 1.11.4 +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.1 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 7.5.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.5.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 8.0.1 +- System.Security.Cryptography.Pkcs 8.0.1 + +#### .NET 9 + +- Azure.Identity 1.11.4 +- Microsoft.Bcl.Cryptography 9.0.0 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.1 +- Microsoft.Extensions.Caching.Memory 9.0.0 +- Microsoft.IdentityModel.JsonWebTokens 7.5.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.5.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 9.0.0 +- System.Security.Cryptography.Pkcs 9.0.0 + +## Contributors +Many thanks to the following public contributors! Their efforts toward furthering this project are very much appreciated! + +- [0xced](https://github.com/0xced) +- [deadlydog](https://github.com/deadlydog) +- [EamonHetherton](https://github.com/EamonHetherton) +- [edwardneal](https://github.com/edwardneal) +- [ErikEJ](https://github.com/ErikEJ) +- [Markeli](https://github.com/Markeli) +- [MichelZ](https://github.com/MichelZ) +- [mus65](https://github.com/mus65) +- [SimonCropp](https://github.com/SimonCropp) +- [TrayanZapryanov](https://github.com/TrayanZapryanov) +- [twsouthwick](https://github.com/twsouthwick) +- [wilbit](https://github.com/wilbit) +- [Wraith2](https://github.com/Wraith2) diff --git a/release-notes/6.0/6.0.1.md b/release-notes/6.0/6.0.1.md new file mode 100644 index 0000000000..6a556273c7 --- /dev/null +++ b/release-notes/6.0/6.0.1.md @@ -0,0 +1,64 @@ +# Release Notes + +## [Stable release 6.0.1] - 2025-01-23 + +This update brings the below changes over the previous stable release: + +### Fixed + +- Fixed reference assembly definitions for SqlClientDiagnostic APIs [#3097](https://github.com/dotnet/SqlClient/pull/3097) +- Fixed issue with down-level SSL/TLS version warnings [#3126](https://github.com/dotnet/SqlClient/pull/3126) + +### Changed + +- Dependency changes + - Updated SNI dependency `Microsoft.Data.SqlClient.SNI` and `Microsoft.Data.SqlClient.SNI.runtime` to `6.0.2` [#3116](https://github.com/dotnet/SqlClient/pull/3116) [#3117](https://github.com/dotnet/SqlClient/pull/3117) + +## Target Platform Support +- .NET Framework 4.6.2+ (Windows ARM64, Windows x64, Windows x86) +- .NET 8.0+ (Linux, macOS, Windows ARM64, Windows x64, Windows x86) + +### Dependencies + +#### .NET Framework + +- Azure.Identity 1.11.4 +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.SNI 6.0.1 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 7.5.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.5.0 +- System.Buffers 4.5.1 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Encodings.Web 6.0.0 +- System.Text.Json 6.0.10 +- System.Data.Common 4.3.0 + +#### .NET 8 + +- Azure.Identity 1.11.4 +- Microsoft.Bcl.Cryptography 8.0.0 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.1 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 7.5.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.5.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 8.0.1 +- System.Security.Cryptography.Pkcs 8.0.1 + +#### .NET 9 + +- Azure.Identity 1.11.4 +- Microsoft.Bcl.Cryptography 9.0.0 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.1 +- Microsoft.Extensions.Caching.Memory 9.0.0 +- Microsoft.IdentityModel.JsonWebTokens 7.5.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.5.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 9.0.0 +- System.Security.Cryptography.Pkcs 9.0.0 + +## Contributors +Special thanks to the following public contributor! + +- [edwardneal](https://github.com/edwardneal) \ No newline at end of file diff --git a/release-notes/6.0/6.0.2.md b/release-notes/6.0/6.0.2.md new file mode 100644 index 0000000000..7d4ae55cd5 --- /dev/null +++ b/release-notes/6.0/6.0.2.md @@ -0,0 +1,63 @@ +# Release Notes + +## [Stable release 6.0.2] - 2025-04-25 + +This update brings the below changes over the previous stable release: + +### Fixed + +- Fixed possible `NullPointerException` during socket receive [#3283](https://github.com/dotnet/SqlClient/pull/3283) +- Fixed reference assembly definitions for SqlJson APIs [#3169](https://github.com/dotnet/SqlClient/pull/3169) +- Fixed an error reading the output parameter of type JSON while executing stored procedure [#3173](https://github.com/dotnet/SqlClient/pull/3173) + +### Changed +- Updated the below dependencies: + - Updated [Microsoft.Bcl.Cryptography](https://www.nuget.org/packages/Microsoft.Bcl.Cryptography/9.0.4) from 9.0.0 to 9.0.4 for .NET 9 targeted dll. [#3281](https://github.com/dotnet/SqlClient/pull/3281) + - Updated [Microsoft.Extensions.Caching.Memory](https://www.nuget.org/packages/Microsoft.Extensions.Caching.Memory/9.0.4) from 9.0.0 to 9.0.4 for .NET 9 targeted dll. [#3281](https://github.com/dotnet/SqlClient/pull/3281) + - Updated [System.Configuration.ConfigurationManager](https://www.nuget.org/packages/System.Configuration.ConfigurationManager/9.0.4) from 9.0.0 to 9.0.4 for .NET 9 targeted dll. [#3281](https://github.com/dotnet/SqlClient/pull/3281) + - Updated [System.Security.Cryptography.Pkcs](https://www.nuget.org/packages/System.Security.Cryptography.Pkcs/9.0.4) from 9.0.0 to 9.0.4 for .NET 9 targeted dll. [#3281](https://github.com/dotnet/SqlClient/pull/3281) + +## Target Platform Support + +- .NET Framework 4.6.2+ (Windows ARM64, Windows x64, Windows x86) +- .NET 8.0+ (Linux, macOS, Windows ARM64, Windows x64, Windows x86) + +### Dependencies + +#### .NET Framework + +- Azure.Identity 1.11.4 +- Microsoft.Bcl.Cryptography 8.0.1 +- Microsoft.Data.SqlClient.SNI 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 7.5.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.5.0 +- System.Buffers 4.5.1 +- System.Security.Cryptography.Pkcs 8.0.1 +- System.Text.Encodings.Web 6.0.0 +- System.Text.Json 6.0.10 +- System.Data.Common 4.3.0 + +#### .NET 8 + +- Azure.Identity 1.11.4 +- Microsoft.Bcl.Cryptography 8.0.1 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 8.0.1 +- Microsoft.IdentityModel.JsonWebTokens 7.5.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.5.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 8.0.1 +- System.Security.Cryptography.Pkcs 8.0.1 + +#### .NET 9 + +- Azure.Identity 1.11.4 +- Microsoft.Bcl.Cryptography 9.0.4 +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Microsoft.Extensions.Caching.Memory 9.0.4 +- Microsoft.IdentityModel.JsonWebTokens 7.5.0 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.5.0 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 9.0.4 +- System.Security.Cryptography.Pkcs 9.0.4 diff --git a/release-notes/6.0/6.0.md b/release-notes/6.0/6.0.md deleted file mode 100644 index 0b4bbd71ed..0000000000 --- a/release-notes/6.0/6.0.md +++ /dev/null @@ -1,8 +0,0 @@ -# Microsoft.Data.SqlClient 6.0 Releases - -The following Microsoft.Data.SqlClient 6.0 preview releases have been shipped: - -| Release Date | Version | Notes | -| :-- | :-- | :--: | -| 2024-08-27 | 6.0.0-preview1.24240.8 | [release notes](6.0.0-preview1.md) | -| 2024-10-30 | 6.0.0-preview2.24304.8 | [release notes](6.0.0-preview2.md) | diff --git a/release-notes/6.0/README.md b/release-notes/6.0/README.md index 0b4bbd71ed..2944bbf6e8 100644 --- a/release-notes/6.0/README.md +++ b/release-notes/6.0/README.md @@ -1,8 +1,17 @@ # Microsoft.Data.SqlClient 6.0 Releases +The following Microsoft.Data.SqlClient 6.0 stable releases have been shipped: + +| Release Date | Version | Notes | +| :-- | :-- | :--: | +| 2025-01-15 | 6.0.0 | [release notes](6.0.0.md) | +| 2025-01-23 | 6.0.1 | [release notes](6.0.1.md) | +| 2025-04-25 | 6.0.2 | [release notes](6.0.2.md) | + The following Microsoft.Data.SqlClient 6.0 preview releases have been shipped: | Release Date | Version | Notes | | :-- | :-- | :--: | | 2024-08-27 | 6.0.0-preview1.24240.8 | [release notes](6.0.0-preview1.md) | | 2024-10-30 | 6.0.0-preview2.24304.8 | [release notes](6.0.0-preview2.md) | +| 2024-11-27 | 6.0.0-preview3.24332.3 | [release notes](6.0.0-preview3.md) | diff --git a/release-notes/6.1/6.1.0-preview1.md b/release-notes/6.1/6.1.0-preview1.md new file mode 100644 index 0000000000..8d3f768712 --- /dev/null +++ b/release-notes/6.1/6.1.0-preview1.md @@ -0,0 +1,97 @@ +# Release Notes + +## [Preview Release 6.1.0-preview1.25120.4] - 2025-04-30 + +This update brings the following changes over the previous release: + +### Contributors +We thank the following public contributors. Their efforts toward this project are very much appreciated. + +- [MichelZ](https://github.com/MichelZ) +- [edwardneal](https://github.com/edwardneal) +- [Wraith2](https://github.com/Wraith2) +- [ErikEJ](https://github.com/ErikEJ) +- [twsouthwick](https://github.com/twsouthwick) +- [emmanuel-ferdman](https://github.com/emmanuel-ferdman) +- [MaceWindu](https://github.com/MaceWindu) +- [JNjenga](https://github.com/JNjenga) +- [Midhunnnk](https://github.com/Midhunnnk) + +### Added +* Added packet multiplexing support to improve large data read performance. [#2714](https://github.com/dotnet/SqlClient/pull/2714) [#3161](https://github.com/dotnet/SqlClient/pull/3161) [#3202](https://github.com/dotnet/SqlClient/pull/3202) +* Added support for special casing with Fabric endpoints. [#3084](https://github.com/dotnet/SqlClient/pull/3084) + +### Fixed +* Fixed distributed transactions to be preserved during pooled connection resets. [#3019](https://github.com/dotnet/SqlClient/pull/3019). +* Fixed application crash when the `Data Source` parameter begins with a comma. [#3250](https://github.com/dotnet/SqlClient/pull/3250). +* Resolved synonym count discrepancies in debug mode. [#3098](https://github.com/dotnet/SqlClient/pull/3098). +* Addressed warnings for down-level SSL/TLS versions. [#3126](https://github.com/dotnet/SqlClient/pull/3126). + +### Changed +* Optimized binary size for AOT. [#3091](https://github.com/dotnet/SqlClient/pull/3091) +* Refined bulk copy operations to handle unmatched column names more effectively. [#3205](https://github.com/dotnet/SqlClient/pull/3205). +* Enhanced `SqlBulkCopy` to explicitly identify mismatched column names. [#3183](https://github.com/dotnet/SqlClient/pull/3183). +* Optimized outgoing SSPI blob handling using `IBufferWriter`. [#2452](https://github.com/dotnet/SqlClient/pull/2452). +* Replaced `byte[]` with `string` for SNI to improve efficiency. [#2790](https://github.com/dotnet/SqlClient/pull/2790). +* Code cleanup to remove SQL 2000 support [#2839](https://github.com/dotnet/SqlClient/pull/2839), [#3206](https://github.com/dotnet/SqlClient/pull/3206), [#3217](https://github.com/dotnet/SqlClient/pull/3217), +* Connection pool design refactor for a modular connection pool design [#3199](https://github.com/dotnet/SqlClient/pull/3199) +* Updated various dependencies [#3229](https://github.com/dotnet/SqlClient/pull/3229), primarily: + - System.Text.Encodings.Web to v8.0.0 + - System.Text.Json to v8.0.5 + - Azure.Identity to v1.13.2 + - Microsoft.Identity.Model.Json.Web.Tokens to v7.7.1 + - Microsoft.Identity.Model.Protocols.OpenIdConnect to v7.7.1 +* Code merge towards a unified SqlClient project, aligning .NET Framework and .NET Core implementations. ([#2957](https://github.com/dotnet/sqlclient/pull/2957), [#2963](https://github.com/dotnet/sqlclient/pull/2963), [#2984](https://github.com/dotnet/sqlclient/pull/2984), [#2982](https://github.com/dotnet/sqlclient/pull/2982), [#3023](https://github.com/dotnet/sqlclient/pull/3023), [#3015](https://github.com/dotnet/sqlclient/pull/3015), [#2967](https://github.com/dotnet/sqlclient/pull/2967), [#3164](https://github.com/dotnet/sqlclient/pull/3164), [#3163](https://github.com/dotnet/sqlclient/pull/3163), [#3171](https://github.com/dotnet/sqlclient/pull/3171), [#3182](https://github.com/dotnet/sqlclient/pull/3182), [#3179](https://github.com/dotnet/sqlclient/pull/3179), [#3156](https://github.com/dotnet/sqlclient/pull/3156), [#3213](https://github.com/dotnet/sqlclient/pull/3213), [#3232](https://github.com/dotnet/sqlclient/pull/3232), [#3236](https://github.com/dotnet/sqlclient/pull/3236), [#3231](https://github.com/dotnet/sqlclient/pull/3231), [#3241](https://github.com/dotnet/sqlclient/pull/3241), [#3246](https://github.com/dotnet/sqlclient/pull/3246), [#3247](https://github.com/dotnet/sqlclient/pull/3247), [#3222](https://github.com/dotnet/sqlclient/pull/3222), [#3255](https://github.com/dotnet/sqlclient/pull/3255), [#3254](https://github.com/dotnet/sqlclient/pull/3254), [#3259](https://github.com/dotnet/sqlclient/pull/3259), [#3264](https://github.com/dotnet/sqlclient/pull/3264), [#3256](https://github.com/dotnet/sqlclient/pull/3256), [#3251](https://github.com/dotnet/sqlclient/pull/3251), [#3275](https://github.com/dotnet/sqlclient/pull/3275), [#3277](https://github.com/dotnet/sqlclient/pull/3277), [#3263](https://github.com/dotnet/sqlclient/pull/3263), [#3292](https://github.com/dotnet/sqlclient/pull/3292), [#3208](https://github.com/dotnet/sqlclient/pull/3208)). +* Test improvements include updates to test references, removal of hardcoded certificates, improved stability, and better coverage ([#3041](https://github.com/dotnet/sqlclient/pull/3041), [#3034](https://github.com/dotnet/sqlclient/pull/3034), [#3130](https://github.com/dotnet/sqlclient/pull/3130), [#3128](https://github.com/dotnet/sqlclient/pull/3128), [#3181](https://github.com/dotnet/sqlclient/pull/3181), [#3060](https://github.com/dotnet/sqlclient/pull/3060), [#3184](https://github.com/dotnet/sqlclient/pull/3184), [#3033](https://github.com/dotnet/sqlclient/pull/3033), [#3186](https://github.com/dotnet/sqlclient/pull/3186), [#3025](https://github.com/dotnet/sqlclient/pull/3025), [#3230](https://github.com/dotnet/sqlclient/pull/3230), [#3237](https://github.com/dotnet/sqlclient/pull/3237), [#3059](https://github.com/dotnet/sqlclient/pull/3059), [#3061](https://github.com/dotnet/sqlclient/pull/3061)). + +## New Contributors +* @emmanuel-ferdman made their first contribution in https://github.com/dotnet/SqlClient/pull/3041 +* @MaceWindu made their first contribution in https://github.com/dotnet/SqlClient/pull/3134 +* @JNjenga made their first contribution in https://github.com/dotnet/SqlClient/pull/3183 +* @Midhunnnk made their first contribution in https://github.com/dotnet/SqlClient/pull/3250 + + +## 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 Framework + +- Microsoft.Data.SqlClient.SNI 6.0.2 +- Azure.Identity 1.13.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.Text.Encodings.Web 8.0.0 +- System.Text.Json 8.0.5 +- System.Data.Common 4.3.0 +- System.Security.Cryptography.Pkcs 8.0.1 +- Microsoft.Bcl.Cryptography 8.0.0 + +#### .NET 8 + +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Azure.Identity 1.13.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 +- Microsoft.Bcl.Cryptography 8.0.0 + +#### .NET 9 + +- Microsoft.Data.SqlClient.SNI.runtime 6.0.2 +- Azure.Identity 1.13.2 +- Microsoft.Extensions.Caching.Memory 9.0.4 +- Microsoft.IdentityModel.JsonWebTokens 7.7.1 +- Microsoft.IdentityModel.Protocols.OpenIdConnect 7.7.1 +- Microsoft.SqlServer.Server 1.0.0 +- System.Configuration.ConfigurationManager 9.0.4 +- System.Security.Cryptography.Pkcs 9.0.4 +- Microsoft.Bcl.Cryptography 9.0.4 diff --git a/release-notes/6.1/README.md b/release-notes/6.1/README.md new file mode 100644 index 0000000000..c973a22f98 --- /dev/null +++ b/release-notes/6.1/README.md @@ -0,0 +1,7 @@ +# Microsoft.Data.SqlClient 6.0 Releases + +The following Microsoft.Data.SqlClient 6.1 preview releases have been shipped: + +| Release Date | Version | Notes | +| :-- | :-- | :--: | +| 2025-04-30 | 6.1.0-preview1.25120.4 | [release notes](6.1.0-preview1.md) | diff --git a/release-notes/README.md b/release-notes/README.md index 2565fffc72..a9e62ae167 100644 --- a/release-notes/README.md +++ b/release-notes/README.md @@ -1,9 +1,10 @@ # Microsoft.Data.SqlClient Release Notes -The latest stable release is [Microsoft.Data.SqlClient 5.2](5.2). +The latest stable release is [Microsoft.Data.SqlClient 6.0](6.0). ## Release Information +- [Microsoft.Data.SqlClient 6.1](6.1) - [Microsoft.Data.SqlClient 6.0](6.0) - [Microsoft.Data.SqlClient 5.2](5.2) - [Microsoft.Data.SqlClient 5.1](5.1) diff --git a/roadmap.md b/roadmap.md index 4771802b4f..47b7532551 100644 --- a/roadmap.md +++ b/roadmap.md @@ -4,26 +4,15 @@ The Microsoft.Data.SqlClient roadmap communicates project priorities for evolvin ## Released Versions -- [Release Notes](https://github.com/dotnet/sqlclient/blob/master/release-notes/README.md) - For detailed release notes summarizing of all changes/features released. +- [Release Notes](release-notes/README.md) - For detailed release notes summarizing of all changes/features released. - [GitHub Releases](https://github.com/dotnet/sqlclient/releases) - For downloading NuGet Packages and identifying driver releases with changelog notes. ## Upcoming Ship Dates -| Milestone | Release Date | Project Board | -|---------------------------|--------------|---------------| -| Microsoft.Data.SqlClient v1.1 (servicing) | As needed (see also [1.1 releases](https://github.com/dotnet/sqlclient/blob/master/release-notes/1.1)) | Closed | -| Microsoft.Data.SqlClient v2.1 (servicing) | As needed (see also [2.1 releases](https://github.com/dotnet/sqlclient/blob/master/release-notes/2.1)) | Closed | -| Microsoft.Data.SqlClient v3.0 (servicing) | As needed (see also [3.0 releases](https://github.com/dotnet/sqlclient/blob/master/release-notes/3.0)) | Closed | -| Microsoft.Data.SqlClient v4.0 (servicing) | As needed (see also [4.0 releases](https://github.com/dotnet/sqlclient/blob/master/release-notes/4.0)) | Closed | -| Microsoft.Data.SqlClient v4.1 (servicing) | As needed (see also [4.1 releases](https://github.com/dotnet/sqlclient/blob/master/release-notes/4.1)) | Closed | -| Microsoft.Data.SqlClient v5.0 | GA (General Availability) estimated for May 2022 | [SqlClient 5.0.0](https://github.com/dotnet/SqlClient/projects/9) - -> Note: Dates are calendar year (as opposed to fiscal year). +For upcoming release dates, see [SqlClient milestones](https://github.com/dotnet/SqlClient/milestones). [SqlClient Projects](https://github.com/dotnet/SqlClient/projects) are used by our development team to define roadmap of prioritized work. SqlClient Boards contain issues, pull requests, and other features, development activities that are categorized as cards. Our goal is to be transparent over priorities, work in progress and deliverables. -Milestone information is also available for GitHub Issues and Pull Requests [dotnet/sqlclient milestones](https://github.com/dotnet/sqlclient/milestones). - ## Feedback The best way to give feedback is to create issues in the [dotnet/sqlclient](https://github.com/dotnet/sqlclient) repo. diff --git a/spec/user-agent-v1.jsonc b/spec/user-agent-v1.jsonc new file mode 100644 index 0000000000..d2c374df9f --- /dev/null +++ b/spec/user-agent-v1.jsonc @@ -0,0 +1,94 @@ +{ + // This is the JSON Schema that defines the format of the TDS USERAGENT + // Feature Extension payload (version 1) sent to the server during login. + // + // Feature Extension Name: USERAGENT + // Feature Extension Version: 1 + // Schema Version: 1 + // + // The design document for version 1 is here: + // + // https://microsoft.sharepoint-df.com/:w:/t/sqldevx/ERIWTt0zlCxLroNHyaPlKYwBI_LNSff6iy_wXZ8xX6nctQ?e=iP8q75 + + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:microsoft:mssql:user-agent:v1", + "title": "Driver User Agent V1", + "description": "The user agent payload sent by the driver during login.", + + "type": "object", + "properties": + { + "driver": + { + "enum": ["MS-JDBC", "MS-MDS", "MS-ODBC", "MS-OLEDB", "MS-PHP", "MS-PYTHON"], + "description": "The type of driver." + }, + "version": + { + "type": "string", + "description": "The version of the driver, as a semantic version.", + // See: + // + // https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string + // + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$" + }, + "os": + { + "type": "object", + "description": "Information about the operating system.", + "properties": + { + "type": + { + "enum": ["Windows", "Linux", "macOS", "FreeBSD", "Android", "Unknown"], + "description": "The type of operating system." + }, + "details": + { + "type": "string", + "description": "Extended details of the operating system." + } + }, + "additionalProperties": false, + "required": ["type", "details"] + }, + "arch": + { + "type": "string", + "description": "The architecture of the driver process." + }, + "runtime": + { + "type": "string", + "description": "The runtime environment of the driver." + } + }, + "additionalProperties": false, + "required": ["driver", "version", "os", "arch", "runtime"], + "examples": + [ + { + "driver": "MS-MDS", + "version": "6.0.2", + "os": + { + "type": "Linux", + "details": "Debian GNU Linux 12.2 Bookworm" + }, + "arch": "amd64", + "runtime": ".NET 9.0.3" + }, + { + "driver": "MS-JDBC", + "version": "11.2.0", + "os": + { + "type": "Windows", + "details": "Windows 10 Pro 22H2" + }, + "arch": "x64", + "runtime": "Java 17.0.8+7-LTS" + } + ] +} diff --git a/src/.dockerignore b/src/.dockerignore deleted file mode 100644 index e7b690f114..0000000000 --- a/src/.dockerignore +++ /dev/null @@ -1,25 +0,0 @@ -**/.classpath -**/.dockerignore -**/.env -**/.git -**/.gitignore -**/.project -**/.settings -**/.toolstarget -**/.vs -**/.vscode -**/*.*proj.user -**/*.dbmdl -**/*.jfm -**/azds.yaml -**/bin -**/charts -**/docker-compose* -**/Dockerfile* -**/node_modules -**/npm-debug.log -**/obj -**/secrets.dev.yaml -**/values.dev.yaml -LICENSE -README.md diff --git a/src/Directory.Build.props b/src/Directory.Build.props index b01737f892..73570b80df 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,7 +1,6 @@ - 9.0 true false true @@ -27,8 +26,8 @@ $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) - - + + $(DefineConstants);DEBUG @@ -70,8 +69,18 @@ $(DefineConstants);ENCLAVE_SIMULATOR - - + + + + + false + all @@ -87,14 +96,14 @@ true - + - + @@ -102,4 +111,25 @@ false + + + + 13.0 + diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props new file mode 100644 index 0000000000..5cd85e5a1f --- /dev/null +++ b/src/Directory.Packages.props @@ -0,0 +1,69 @@ + + + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient.sln b/src/Microsoft.Data.SqlClient.sln index 78a38c76b0..e4d29d999c 100644 --- a/src/Microsoft.Data.SqlClient.sln +++ b/src/Microsoft.Data.SqlClient.sln @@ -12,10 +12,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TDS.Servers", "Microsoft.Da EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TDS", "Microsoft.Data.SqlClient\tests\tools\TDS\TDS\TDS.csproj", "{8DC9D1A0-351B-47BC-A90F-B9DA542550E9}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient.Tests", "Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.Tests.csproj", "{D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}" - ProjectSection(ProjectDependencies) = postProject - {37431336-5307-4184-9356-C4B7E47DC714} = {37431336-5307-4184-9356-C4B7E47DC714} - EndProjectSection +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient.FunctionalTests", "Microsoft.Data.SqlClient\tests\FunctionalTests\Microsoft.Data.SqlClient.FunctionalTests.csproj", "{D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient", "Microsoft.Data.SqlClient\netcore\src\Microsoft.Data.SqlClient.csproj", "{37431336-5307-4184-9356-C4B7E47DC714}" EndProject @@ -64,8 +61,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient.Al {412BCCC8-19F6-489A-B594-E9A506816155} = {412BCCC8-19F6-489A-B594-E9A506816155} EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "add-ons", "add-ons", "{C9726AED-D6A3-4AAC-BA04-92DD1F079594}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{ED952CF7-84DF-437A-B066-F516E9BE1C2C}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "snippets", "snippets", "{71F356DC-DFA3-4163-8BFE-D268722CE189}" @@ -177,10 +172,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.Data.SqlTypes", " ProjectSection(SolutionItems) = preProject ..\doc\snippets\Microsoft.Data.SqlTypes\SqlFileStream.xml = ..\doc\snippets\Microsoft.Data.SqlTypes\SqlFileStream.xml ..\doc\snippets\Microsoft.Data.SqlTypes\SqlJson.xml = ..\doc\snippets\Microsoft.Data.SqlTypes\SqlJson.xml + ..\doc\snippets\Microsoft.Data.SqlTypes\SqlVector.xml = ..\doc\snippets\Microsoft.Data.SqlTypes\SqlVector.xml EndProjectSection EndProject -Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{F5DF2FDC-C860-4CB3-8B24-7C903C6FC076}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient.TestUtilities", "Microsoft.Data.SqlClient\tests\tools\Microsoft.Data.SqlClient.TestUtilities\Microsoft.Data.SqlClient.TestUtilities.csproj", "{89D6D382-9B36-43C9-A912-03802FDA8E36}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient.ExtUtilities", "Microsoft.Data.SqlClient\tests\tools\Microsoft.Data.SqlClient.ExtUtilities\Microsoft.Data.SqlClient.ExtUtilities.csproj", "{E4C08DCE-DC29-4FEB-B655-1E7287DB5A2B}" @@ -190,8 +184,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomRetryLogicProvider", {37431336-5307-4184-9356-C4B7E47DC714} = {37431336-5307-4184-9356-C4B7E47DC714} EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient.DockerLinuxTest", "Microsoft.Data.SqlClient\tests\Docker\DockerLinuxTest\Microsoft.Data.SqlClient.DockerLinuxTest.csproj", "{B93A3149-67E8-491E-A1E5-19D65F9D9E98}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient.PerformanceTests", "Microsoft.Data.SqlClient\tests\PerformanceTests\Microsoft.Data.SqlClient.PerformanceTests.csproj", "{599A336B-2A5F-473D-8442-1223ED37C93E}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4F3CD363-B1E6-4D6D-9466-97D78A56BE45}" @@ -219,6 +211,98 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestClr", "TestClr", "{CDE5 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Data.SqlClient", "Microsoft.Data.SqlClient\src\Microsoft.Data.SqlClient.csproj", "{9A8996A8-6484-4AA7-B50F-F861430EDE2F}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "eng", "eng", "{4600328C-C134-499F-AAE2-964E8AD5472C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pipelines", "pipelines", "{4CAE9195-4F1A-4D48-854C-1C9FBC512C66}" + ProjectSection(SolutionItems) = preProject + ..\eng\pipelines\akv-official-pipeline.yml = ..\eng\pipelines\akv-official-pipeline.yml + ..\eng\pipelines\dotnet-sqlclient-ci-core.yml = ..\eng\pipelines\dotnet-sqlclient-ci-core.yml + ..\eng\pipelines\dotnet-sqlclient-ci-package-reference-pipeline.yml = ..\eng\pipelines\dotnet-sqlclient-ci-package-reference-pipeline.yml + ..\eng\pipelines\dotnet-sqlclient-ci-project-reference-pipeline.yml = ..\eng\pipelines\dotnet-sqlclient-ci-project-reference-pipeline.yml + ..\eng\pipelines\dotnet-sqlclient-signing-pipeline.yml = ..\eng\pipelines\dotnet-sqlclient-signing-pipeline.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{FD4D7A96-79B1-4F89-B64D-29FACCC9232F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{E76A4ED5-9137-4E4B-AE91-7AEDB2683823}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "jobs", "jobs", "{3850810F-535E-443A-A9B4-7EE85B327E0A}" + ProjectSection(SolutionItems) = preProject + ..\eng\pipelines\common\templates\jobs\build-signed-package-job.yml = ..\eng\pipelines\common\templates\jobs\build-signed-package-job.yml + ..\eng\pipelines\common\templates\jobs\ci-build-nugets-job.yml = ..\eng\pipelines\common\templates\jobs\ci-build-nugets-job.yml + ..\eng\pipelines\common\templates\jobs\ci-code-coverage-job.yml = ..\eng\pipelines\common\templates\jobs\ci-code-coverage-job.yml + ..\eng\pipelines\common\templates\jobs\ci-run-tests-job.yml = ..\eng\pipelines\common\templates\jobs\ci-run-tests-job.yml + ..\eng\pipelines\common\templates\jobs\run-tests-package-reference-job.yml = ..\eng\pipelines\common\templates\jobs\run-tests-package-reference-job.yml + ..\eng\pipelines\common\templates\jobs\validate-signed-package-job.yml = ..\eng\pipelines\common\templates\jobs\validate-signed-package-job.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "stages", "stages", "{01FA4774-E594-4A2D-B839-EA7A56D09A8B}" + ProjectSection(SolutionItems) = preProject + ..\eng\pipelines\common\templates\stages\ci-run-tests-stage.yml = ..\eng\pipelines\common\templates\stages\ci-run-tests-stage.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "steps", "steps", "{EABE3A3E-D505-418A-B1B8-1B0AC4872F3D}" + ProjectSection(SolutionItems) = preProject + ..\eng\pipelines\common\templates\steps\build-all-configurations-signed-dlls-step.yml = ..\eng\pipelines\common\templates\steps\build-all-configurations-signed-dlls-step.yml + ..\eng\pipelines\common\templates\steps\build-all-tests-step.yml = ..\eng\pipelines\common\templates\steps\build-all-tests-step.yml + ..\eng\pipelines\common\templates\steps\build-and-run-tests-netcore-step.yml = ..\eng\pipelines\common\templates\steps\build-and-run-tests-netcore-step.yml + ..\eng\pipelines\common\templates\steps\build-and-run-tests-netfx-step.yml = ..\eng\pipelines\common\templates\steps\build-and-run-tests-netfx-step.yml + ..\eng\pipelines\common\templates\steps\ci-prebuild-step.yml = ..\eng\pipelines\common\templates\steps\ci-prebuild-step.yml + ..\eng\pipelines\common\templates\steps\ci-project-build-step.yml = ..\eng\pipelines\common\templates\steps\ci-project-build-step.yml + ..\eng\pipelines\common\templates\steps\code-analyze-step.yml = ..\eng\pipelines\common\templates\steps\code-analyze-step.yml + ..\eng\pipelines\common\templates\steps\configure-sql-server-linux-step.yml = ..\eng\pipelines\common\templates\steps\configure-sql-server-linux-step.yml + ..\eng\pipelines\common\templates\steps\configure-sql-server-macos-step.yml = ..\eng\pipelines\common\templates\steps\configure-sql-server-macos-step.yml + ..\eng\pipelines\common\templates\steps\configure-sql-server-step.yml = ..\eng\pipelines\common\templates\steps\configure-sql-server-step.yml + ..\eng\pipelines\common\templates\steps\configure-sql-server-win-step.yml = ..\eng\pipelines\common\templates\steps\configure-sql-server-win-step.yml + ..\eng\pipelines\common\templates\steps\copy-dlls-for-test-step.yml = ..\eng\pipelines\common\templates\steps\copy-dlls-for-test-step.yml + ..\eng\pipelines\common\templates\steps\ensure-dotnet-version.yml = ..\eng\pipelines\common\templates\steps\ensure-dotnet-version.yml + ..\eng\pipelines\common\templates\steps\esrp-code-signing-step.yml = ..\eng\pipelines\common\templates\steps\esrp-code-signing-step.yml + ..\eng\pipelines\common\templates\steps\generate-nuget-package-step.yml = ..\eng\pipelines\common\templates\steps\generate-nuget-package-step.yml + ..\eng\pipelines\common\templates\steps\override-sni-version.yml = ..\eng\pipelines\common\templates\steps\override-sni-version.yml + ..\eng\pipelines\common\templates\steps\pre-build-step.yml = ..\eng\pipelines\common\templates\steps\pre-build-step.yml + ..\eng\pipelines\common\templates\steps\prepare-test-db-step.yml = ..\eng\pipelines\common\templates\steps\prepare-test-db-step.yml + ..\eng\pipelines\common\templates\steps\publish-symbols-step.yml = ..\eng\pipelines\common\templates\steps\publish-symbols-step.yml + ..\eng\pipelines\common\templates\steps\publish-test-results-step.yml = ..\eng\pipelines\common\templates\steps\publish-test-results-step.yml + ..\eng\pipelines\common\templates\steps\run-all-tests-step.yml = ..\eng\pipelines\common\templates\steps\run-all-tests-step.yml + ..\eng\pipelines\common\templates\steps\update-config-file-step.yml = ..\eng\pipelines\common\templates\steps\update-config-file-step.yml + ..\eng\pipelines\common\templates\steps\update-nuget-config-local-feed-step.yml = ..\eng\pipelines\common\templates\steps\update-nuget-config-local-feed-step.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "libraries", "libraries", "{75BAE755-3A1F-41F2-9176-9F8FF9FEE2DD}" + ProjectSection(SolutionItems) = preProject + ..\eng\pipelines\libraries\build-variables.yml = ..\eng\pipelines\libraries\build-variables.yml + ..\eng\pipelines\libraries\ci-build-variables.yml = ..\eng\pipelines\libraries\ci-build-variables.yml + ..\eng\pipelines\libraries\common-variables.yml = ..\eng\pipelines\libraries\common-variables.yml + ..\eng\pipelines\libraries\mds-validation-variables.yml = ..\eng\pipelines\libraries\mds-validation-variables.yml + ..\eng\pipelines\libraries\mds-variables.yml = ..\eng\pipelines\libraries\mds-variables.yml + ..\eng\pipelines\libraries\variables.yml = ..\eng\pipelines\libraries\variables.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "variables", "variables", "{1C796A8E-3529-481B-9D4F-88C67D03DCA2}" + ProjectSection(SolutionItems) = preProject + ..\eng\pipelines\variables\akv-official-variables.yml = ..\eng\pipelines\variables\akv-official-variables.yml + ..\eng\pipelines\variables\common-variables.yml = ..\eng\pipelines\variables\common-variables.yml + ..\eng\pipelines\variables\esrp-signing-variables.yml = ..\eng\pipelines\variables\esrp-signing-variables.yml + ..\eng\pipelines\variables\onebranch-variables.yml = ..\eng\pipelines\variables\onebranch-variables.yml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "jobs", "jobs", "{09352F1D-878F-4F55-8AA2-6E47F1AD37D5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "steps", "steps", "{AD738BD4-6A02-4B88-8F93-FBBBA49A74C8}" + ProjectSection(SolutionItems) = preProject + ..\eng\pipelines\steps\compound-build-akv-step.yml = ..\eng\pipelines\steps\compound-build-akv-step.yml + ..\eng\pipelines\steps\compound-esrp-code-signing-step.yml = ..\eng\pipelines\steps\compound-esrp-code-signing-step.yml + ..\eng\pipelines\steps\compound-extract-akv-apiscan-files-step.yml = ..\eng\pipelines\steps\compound-extract-akv-apiscan-files-step.yml + ..\eng\pipelines\steps\compound-nuget-pack-step.yml = ..\eng\pipelines\steps\compound-nuget-pack-step.yml + ..\eng\pipelines\steps\compound-publish-symbols-step.yml = ..\eng\pipelines\steps\compound-publish-symbols-step.yml + ..\eng\pipelines\steps\roslyn-analyzers-akv-step.yml = ..\eng\pipelines\steps\roslyn-analyzers-akv-step.yml + ..\eng\pipelines\steps\script-output-environment-variables-step.yml = ..\eng\pipelines\steps\script-output-environment-variables-step.yml + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Data.SqlClient.UnitTests", "Microsoft.Data.SqlClient\tests\UnitTests\Microsoft.Data.SqlClient.UnitTests.csproj", "{4461063D-2F2B-274C-7E6F-F235119D258E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Microsoft.Data.SqlClient\tests\Common\Common.csproj", "{67128EC0-30F5-6A98-448B-55F88A1DE707}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -279,16 +363,16 @@ Global {8DC9D1A0-351B-47BC-A90F-B9DA542550E9}.Release|x86.Build.0 = Release|Any CPU {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Debug|x64.ActiveCfg = Debug|Any CPU - {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Debug|x64.Build.0 = Debug|Any CPU - {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Debug|x86.ActiveCfg = Debug|Any CPU - {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Debug|x86.Build.0 = Debug|Any CPU + {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Debug|x64.ActiveCfg = Debug|x64 + {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Debug|x64.Build.0 = Debug|x64 + {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Debug|x86.ActiveCfg = Debug|x86 + {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Debug|x86.Build.0 = Debug|x86 {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Release|Any CPU.ActiveCfg = Release|Any CPU {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Release|Any CPU.Build.0 = Release|Any CPU - {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Release|x64.ActiveCfg = Release|Any CPU - {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Release|x64.Build.0 = Release|Any CPU - {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Release|x86.ActiveCfg = Release|Any CPU - {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Release|x86.Build.0 = Release|Any CPU + {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Release|x64.ActiveCfg = Release|x64 + {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Release|x64.Build.0 = Release|x64 + {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Release|x86.ActiveCfg = Release|x86 + {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9}.Release|x86.Build.0 = Release|x86 {37431336-5307-4184-9356-C4B7E47DC714}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {37431336-5307-4184-9356-C4B7E47DC714}.Debug|Any CPU.Build.0 = Debug|Any CPU {37431336-5307-4184-9356-C4B7E47DC714}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -397,16 +481,6 @@ Global {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Release|x64.Build.0 = Release|Any CPU {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Release|x86.ActiveCfg = Release|Any CPU {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7}.Release|x86.Build.0 = Release|Any CPU - {F5DF2FDC-C860-4CB3-8B24-7C903C6FC076}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F5DF2FDC-C860-4CB3-8B24-7C903C6FC076}.Debug|x64.ActiveCfg = Debug|Any CPU - {F5DF2FDC-C860-4CB3-8B24-7C903C6FC076}.Debug|x64.Build.0 = Debug|Any CPU - {F5DF2FDC-C860-4CB3-8B24-7C903C6FC076}.Debug|x86.ActiveCfg = Debug|Any CPU - {F5DF2FDC-C860-4CB3-8B24-7C903C6FC076}.Debug|x86.Build.0 = Debug|Any CPU - {F5DF2FDC-C860-4CB3-8B24-7C903C6FC076}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F5DF2FDC-C860-4CB3-8B24-7C903C6FC076}.Release|x64.ActiveCfg = Release|Any CPU - {F5DF2FDC-C860-4CB3-8B24-7C903C6FC076}.Release|x64.Build.0 = Release|Any CPU - {F5DF2FDC-C860-4CB3-8B24-7C903C6FC076}.Release|x86.ActiveCfg = Release|Any CPU - {F5DF2FDC-C860-4CB3-8B24-7C903C6FC076}.Release|x86.Build.0 = Release|Any CPU {89D6D382-9B36-43C9-A912-03802FDA8E36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {89D6D382-9B36-43C9-A912-03802FDA8E36}.Debug|Any CPU.Build.0 = Debug|Any CPU {89D6D382-9B36-43C9-A912-03802FDA8E36}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -443,18 +517,6 @@ Global {B499E477-C9B1-4087-A5CF-5C762D90E433}.Release|x64.Build.0 = Release|x64 {B499E477-C9B1-4087-A5CF-5C762D90E433}.Release|x86.ActiveCfg = Release|x86 {B499E477-C9B1-4087-A5CF-5C762D90E433}.Release|x86.Build.0 = Release|x86 - {B93A3149-67E8-491E-A1E5-19D65F9D9E98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B93A3149-67E8-491E-A1E5-19D65F9D9E98}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B93A3149-67E8-491E-A1E5-19D65F9D9E98}.Debug|x64.ActiveCfg = Debug|Any CPU - {B93A3149-67E8-491E-A1E5-19D65F9D9E98}.Debug|x64.Build.0 = Debug|Any CPU - {B93A3149-67E8-491E-A1E5-19D65F9D9E98}.Debug|x86.ActiveCfg = Debug|Any CPU - {B93A3149-67E8-491E-A1E5-19D65F9D9E98}.Debug|x86.Build.0 = Debug|Any CPU - {B93A3149-67E8-491E-A1E5-19D65F9D9E98}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B93A3149-67E8-491E-A1E5-19D65F9D9E98}.Release|Any CPU.Build.0 = Release|Any CPU - {B93A3149-67E8-491E-A1E5-19D65F9D9E98}.Release|x64.ActiveCfg = Release|Any CPU - {B93A3149-67E8-491E-A1E5-19D65F9D9E98}.Release|x64.Build.0 = Release|Any CPU - {B93A3149-67E8-491E-A1E5-19D65F9D9E98}.Release|x86.ActiveCfg = Release|Any CPU - {B93A3149-67E8-491E-A1E5-19D65F9D9E98}.Release|x86.Build.0 = Release|Any CPU {599A336B-2A5F-473D-8442-1223ED37C93E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {599A336B-2A5F-473D-8442-1223ED37C93E}.Debug|Any CPU.Build.0 = Debug|Any CPU {599A336B-2A5F-473D-8442-1223ED37C93E}.Debug|x64.ActiveCfg = Debug|x64 @@ -485,6 +547,30 @@ Global {9A8996A8-6484-4AA7-B50F-F861430EDE2F}.Release|Any CPU.ActiveCfg = Release|Any CPU {9A8996A8-6484-4AA7-B50F-F861430EDE2F}.Release|x64.ActiveCfg = Release|Any CPU {9A8996A8-6484-4AA7-B50F-F861430EDE2F}.Release|x86.ActiveCfg = Release|Any CPU + {4461063D-2F2B-274C-7E6F-F235119D258E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4461063D-2F2B-274C-7E6F-F235119D258E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4461063D-2F2B-274C-7E6F-F235119D258E}.Debug|x64.ActiveCfg = Debug|x64 + {4461063D-2F2B-274C-7E6F-F235119D258E}.Debug|x64.Build.0 = Debug|x64 + {4461063D-2F2B-274C-7E6F-F235119D258E}.Debug|x86.ActiveCfg = Debug|x86 + {4461063D-2F2B-274C-7E6F-F235119D258E}.Debug|x86.Build.0 = Debug|x86 + {4461063D-2F2B-274C-7E6F-F235119D258E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4461063D-2F2B-274C-7E6F-F235119D258E}.Release|Any CPU.Build.0 = Release|Any CPU + {4461063D-2F2B-274C-7E6F-F235119D258E}.Release|x64.ActiveCfg = Release|x64 + {4461063D-2F2B-274C-7E6F-F235119D258E}.Release|x64.Build.0 = Release|x64 + {4461063D-2F2B-274C-7E6F-F235119D258E}.Release|x86.ActiveCfg = Release|x86 + {4461063D-2F2B-274C-7E6F-F235119D258E}.Release|x86.Build.0 = Release|x86 + {67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|x64.ActiveCfg = Debug|x64 + {67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|x64.Build.0 = Debug|x64 + {67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|x86.ActiveCfg = Debug|x86 + {67128EC0-30F5-6A98-448B-55F88A1DE707}.Debug|x86.Build.0 = Debug|x86 + {67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|Any CPU.Build.0 = Release|Any CPU + {67128EC0-30F5-6A98-448B-55F88A1DE707}.Release|x64.ActiveCfg = Release|x64 + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -496,6 +582,8 @@ Global {8DC9D1A0-351B-47BC-A90F-B9DA542550E9} = {0CC4817A-12F3-4357-912C-09315FAAD008} {D2D1E2D1-B6E0-489F-A36D-1F3047AB87B9} = {0CC4817A-12F3-4357-912C-09315FAAD008} {37431336-5307-4184-9356-C4B7E47DC714} = {28E5EFE6-C9DD-4FF9-9FEC-532F72DFFA6E} + {28E5EFE6-C9DD-4FF9-9FEC-532F72DFFA6E} = {4F3CD363-B1E6-4D6D-9466-97D78A56BE45} + {3FDD425C-FE01-4B56-863E-1FCDD0677CF5} = {4F3CD363-B1E6-4D6D-9466-97D78A56BE45} {D1392B54-998A-4F27-BC17-4CE149117BCC} = {CDE508A5-F5D0-4A59-A4EF-978833830727} {45DB5F86-7AE3-45C6-870D-F9357B66BDB5} = {0CC4817A-12F3-4357-912C-09315FAAD008} {6C88F00F-9597-43AD-9E5F-9B344DA3B16F} = {CDE508A5-F5D0-4A59-A4EF-978833830727} @@ -505,7 +593,7 @@ Global {1C9FC4B8-54BC-4B6C-BB3A-F5CD59D80A9B} = {A2E7E470-5EFF-4828-B55E-FCBA3650F51C} {771F3F1E-7A68-4A9D-ADA8-A24F1D5BE71D} = {3FDD425C-FE01-4B56-863E-1FCDD0677CF5} {412BCCC8-19F6-489A-B594-E9A506816155} = {771F3F1E-7A68-4A9D-ADA8-A24F1D5BE71D} - {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7} = {C9726AED-D6A3-4AAC-BA04-92DD1F079594} + {9073ABEF-92E0-4702-BB23-2C99CEF9BDD7} = {4F3CD363-B1E6-4D6D-9466-97D78A56BE45} {71F356DC-DFA3-4163-8BFE-D268722CE189} = {ED952CF7-84DF-437A-B066-F516E9BE1C2C} {908C7DD3-C999-40A6-9433-9F5ACA7C36F5} = {71F356DC-DFA3-4163-8BFE-D268722CE189} {0CE216CE-8072-4985-B248-61F0D0BE9C2E} = {71F356DC-DFA3-4163-8BFE-D268722CE189} @@ -516,12 +604,23 @@ Global {89D6D382-9B36-43C9-A912-03802FDA8E36} = {0CC4817A-12F3-4357-912C-09315FAAD008} {E4C08DCE-DC29-4FEB-B655-1E7287DB5A2B} = {0CC4817A-12F3-4357-912C-09315FAAD008} {B499E477-C9B1-4087-A5CF-5C762D90E433} = {0CC4817A-12F3-4357-912C-09315FAAD008} - {B93A3149-67E8-491E-A1E5-19D65F9D9E98} = {0CC4817A-12F3-4357-912C-09315FAAD008} {599A336B-2A5F-473D-8442-1223ED37C93E} = {0CC4817A-12F3-4357-912C-09315FAAD008} {A314812A-7820-4565-A2A8-ABBE391C11E4} = {4F3CD363-B1E6-4D6D-9466-97D78A56BE45} {869A9BCC-D303-4365-9BF7-958CD6387916} = {71F356DC-DFA3-4163-8BFE-D268722CE189} {CDE508A5-F5D0-4A59-A4EF-978833830727} = {0CC4817A-12F3-4357-912C-09315FAAD008} {9A8996A8-6484-4AA7-B50F-F861430EDE2F} = {4F3CD363-B1E6-4D6D-9466-97D78A56BE45} + {4CAE9195-4F1A-4D48-854C-1C9FBC512C66} = {4600328C-C134-499F-AAE2-964E8AD5472C} + {FD4D7A96-79B1-4F89-B64D-29FACCC9232F} = {4CAE9195-4F1A-4D48-854C-1C9FBC512C66} + {E76A4ED5-9137-4E4B-AE91-7AEDB2683823} = {FD4D7A96-79B1-4F89-B64D-29FACCC9232F} + {3850810F-535E-443A-A9B4-7EE85B327E0A} = {E76A4ED5-9137-4E4B-AE91-7AEDB2683823} + {01FA4774-E594-4A2D-B839-EA7A56D09A8B} = {E76A4ED5-9137-4E4B-AE91-7AEDB2683823} + {EABE3A3E-D505-418A-B1B8-1B0AC4872F3D} = {E76A4ED5-9137-4E4B-AE91-7AEDB2683823} + {75BAE755-3A1F-41F2-9176-9F8FF9FEE2DD} = {4CAE9195-4F1A-4D48-854C-1C9FBC512C66} + {1C796A8E-3529-481B-9D4F-88C67D03DCA2} = {4CAE9195-4F1A-4D48-854C-1C9FBC512C66} + {09352F1D-878F-4F55-8AA2-6E47F1AD37D5} = {4CAE9195-4F1A-4D48-854C-1C9FBC512C66} + {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} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {01D48116-37A2-4D33-B9EC-94793C702431} diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AzureSqlKeyCryptographer.cs b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AzureSqlKeyCryptographer.cs index 0dff6ac786..c4e9eb8396 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AzureSqlKeyCryptographer.cs +++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/AzureSqlKeyCryptographer.cs @@ -7,12 +7,12 @@ using Azure.Security.KeyVault.Keys.Cryptography; using System; using System.Collections.Concurrent; -using System.Threading.Tasks; +using System.Threading; using static Azure.Security.KeyVault.Keys.Cryptography.SignatureAlgorithm; namespace Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider { - internal class AzureSqlKeyCryptographer + internal sealed class AzureSqlKeyCryptographer : IDisposable { /// /// TokenCredential to be used with the KeyClient @@ -25,16 +25,14 @@ internal class AzureSqlKeyCryptographer private readonly ConcurrentDictionary _keyClientDictionary = new(); /// - /// Holds references to the fetch key tasks and maps them to their corresponding Azure Key Vault Key Identifier (URI). - /// These tasks will be used for returning the key in the event that the fetch task has not finished depositing the - /// key into the key dictionary. + /// Holds references to the Azure Key Vault keys and maps them to their corresponding Azure Key Vault Key Identifier (URI). /// - private readonly ConcurrentDictionary>> _keyFetchTaskDictionary = new(); + private readonly ConcurrentDictionary _keyDictionary = new(); /// - /// Holds references to the Azure Key Vault keys and maps them to their corresponding Azure Key Vault Key Identifier (URI). + /// SemaphoreSlim to ensure thread safety when accessing the key dictionary or making network calls to Azure Key Vault to fetch keys. /// - private readonly ConcurrentDictionary _keyDictionary = new(); + private SemaphoreSlim _keyDictionarySemaphore = new(1, 1); /// /// Holds references to the Azure Key Vault CryptographyClient objects and maps them to their corresponding Azure Key Vault Key Identifier (URI). @@ -50,20 +48,44 @@ internal AzureSqlKeyCryptographer(TokenCredential tokenCredential) TokenCredential = tokenCredential; } + /// + /// Disposes the SemaphoreSlim used for thread safety. + /// + public void Dispose() + { + _keyDictionarySemaphore.Dispose(); + } + /// /// Adds the key, specified by the Key Identifier URI, to the cache. + /// Validates the key type and fetches the key from Azure Key Vault if it is not already cached. /// /// internal void AddKey(string keyIdentifierUri) { - if (TheKeyHasNotBeenCached(keyIdentifierUri)) + // Allow only one thread to proceed to ensure thread safety + // as we will need to fetch key information from Azure Key Vault if the key is not found in cache. + _keyDictionarySemaphore.Wait(); + + try { - ParseAKVPath(keyIdentifierUri, out Uri vaultUri, out string keyName, out string keyVersion); - CreateKeyClient(vaultUri); - FetchKey(vaultUri, keyName, keyVersion, keyIdentifierUri); - } + if (!_keyDictionary.ContainsKey(keyIdentifierUri)) + { + ParseAKVPath(keyIdentifierUri, out Uri vaultUri, out string keyName, out string keyVersion); + + // Fetch the KeyClient for the Key vault URI. + KeyClient keyClient = GetOrCreateKeyClient(vaultUri); + + // Fetch the key from Azure Key Vault. + KeyVaultKey key = FetchKeyFromKeyVault(keyClient, keyName, keyVersion); - bool TheKeyHasNotBeenCached(string k) => !_keyDictionary.ContainsKey(k) && !_keyFetchTaskDictionary.ContainsKey(k); + _keyDictionary.AddOrUpdate(keyIdentifierUri, key, (k, v) => key); + } + } + finally + { + _keyDictionarySemaphore.Release(); + } } /// @@ -75,18 +97,12 @@ internal KeyVaultKey GetKey(string keyIdentifierUri) { if (_keyDictionary.TryGetValue(keyIdentifierUri, out KeyVaultKey key)) { - AKVEventSource.Log.TryTraceEvent("Fetched master key from cache"); + AKVEventSource.Log.TryTraceEvent("Fetched key name={0} from cache", key.Name); return key; } - if (_keyFetchTaskDictionary.TryGetValue(keyIdentifierUri, out Task> task)) - { - AKVEventSource.Log.TryTraceEvent("New Master key fetched."); - return Task.Run(() => task).GetAwaiter().GetResult(); - } - // Not a public exception - not likely to occur. - AKVEventSource.Log.TryTraceEvent("Master key not found."); + AKVEventSource.Log.TryTraceEvent("Key not found; URI={0}", keyIdentifierUri); throw ADP.MasterKeyNotFound(keyIdentifierUri); } @@ -95,10 +111,7 @@ internal KeyVaultKey GetKey(string keyIdentifierUri) /// /// The key vault key identifier URI /// - internal int GetKeySize(string keyIdentifierUri) - { - return GetKey(keyIdentifierUri).Key.N.Length; - } + internal int GetKeySize(string keyIdentifierUri) => GetKey(keyIdentifierUri).Key.N.Length; /// /// Generates signature based on RSA PKCS#v1.5 scheme using a specified Azure Key Vault Key URL. @@ -142,41 +155,50 @@ private CryptographyClient GetCryptographyClient(string keyIdentifierUri) CryptographyClient cryptographyClient = new(GetKey(keyIdentifierUri).Id, TokenCredential); _cryptoClientDictionary.TryAdd(keyIdentifierUri, cryptographyClient); - return cryptographyClient; } /// - /// + /// Fetches the column encryption key from the Azure Key Vault. /// - /// The Azure Key Vault URI + /// The KeyClient instance /// The name of the Azure Key Vault key /// The version of the Azure Key Vault key - /// The Azure Key Vault key identifier - private void FetchKey(Uri vaultUri, string keyName, string keyVersion, string keyResourceUri) + private KeyVaultKey FetchKeyFromKeyVault(KeyClient keyClient, string keyName, string keyVersion) { - Task> fetchKeyTask = FetchKeyFromKeyVault(vaultUri, keyName, keyVersion); - _keyFetchTaskDictionary.AddOrUpdate(keyResourceUri, fetchKeyTask, (k, v) => fetchKeyTask); + AKVEventSource.Log.TryTraceEvent("Fetching key name={0}", keyName); - fetchKeyTask - .ContinueWith(k => ValidateRsaKey(k.GetAwaiter().GetResult())) - .ContinueWith(k => _keyDictionary.AddOrUpdate(keyResourceUri, k.GetAwaiter().GetResult(), (key, v) => k.GetAwaiter().GetResult())); + Azure.Response keyResponse = keyClient?.GetKey(keyName, keyVersion); - Task.Run(() => fetchKeyTask); + // Handle the case where the key response is null or contains an error + // This can happen if the key does not exist or if there is an issue with the KeyClient. + // In such cases, we log the error and throw an exception. + if (keyResponse == null || keyResponse.Value == null || keyResponse.GetRawResponse().IsError) + { + AKVEventSource.Log.TryTraceEvent("Get Key failed to fetch Key from Azure Key Vault for key {0}, version {1}", keyName, keyVersion); + if (keyResponse?.GetRawResponse() is Azure.Response response) + { + AKVEventSource.Log.TryTraceEvent("Response status {0} : {1}", response.Status, response.ReasonPhrase); + } + throw ADP.GetKeyFailed(keyName); + } + + KeyVaultKey key = keyResponse.Value; + + // Validate that the key is of type RSA + key = ValidateRsaKey(key); + return key; } /// - /// Looks up the KeyClient object by it's URI and then fetches the key by name. + /// Gets or creates a KeyClient for the specified Azure Key Vault URI. /// - /// The Azure Key Vault URI - /// Then name of the key - /// Then version of the key + /// Key Identifier URL /// - private Task> FetchKeyFromKeyVault(Uri vaultUri, string keyName, string keyVersion) + private KeyClient GetOrCreateKeyClient(Uri vaultUri) { - _keyClientDictionary.TryGetValue(vaultUri, out KeyClient keyClient); - AKVEventSource.Log.TryTraceEvent("Fetching requested master key: {0}", keyName); - return keyClient?.GetKeyAsync(keyName, keyVersion); + return _keyClientDictionary.GetOrAdd( + vaultUri, (_) => new KeyClient(vaultUri, TokenCredential)); } /// @@ -184,7 +206,7 @@ private void FetchKey(Uri vaultUri, string keyName, string keyVersion, string ke /// /// /// - private KeyVaultKey ValidateRsaKey(KeyVaultKey key) + private static KeyVaultKey ValidateRsaKey(KeyVaultKey key) { if (key.KeyType != KeyType.Rsa && key.KeyType != KeyType.RsaHsm) { @@ -195,18 +217,6 @@ private KeyVaultKey ValidateRsaKey(KeyVaultKey key) return key; } - /// - /// Instantiates and adds a KeyClient to the KeyClient dictionary - /// - /// The Azure Key Vault URI - private void CreateKeyClient(Uri vaultUri) - { - if (!_keyClientDictionary.ContainsKey(vaultUri)) - { - _keyClientDictionary.TryAdd(vaultUri, new KeyClient(vaultUri, TokenCredential)); - } - } - /// /// Validates and parses the Azure Key Vault URI and key name. /// @@ -214,7 +224,7 @@ private void CreateKeyClient(Uri vaultUri) /// The Azure Key Vault URI /// The name of the key /// The version of the key - private void ParseAKVPath(string masterKeyPath, out Uri vaultUri, out string masterKeyName, out string masterKeyVersion) + private static void ParseAKVPath(string masterKeyPath, out Uri vaultUri, out string masterKeyName, out string masterKeyVersion) { Uri masterKeyPathUri = new(masterKeyPath); vaultUri = new Uri(masterKeyPathUri.GetLeftPart(UriPartial.Authority)); diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/LocalCache.cs b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/LocalCache.cs index 3e17f5d951..7fbffe0ae6 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/LocalCache.cs +++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/LocalCache.cs @@ -2,8 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.Extensions.Caching.Memory; using System; +using Microsoft.Extensions.Caching.Memory; using static System.Math; namespace Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider @@ -92,6 +92,7 @@ internal TValue GetOrCreate(TKey key, Func createItem) /// /// Determines whether the LocalCache contains the specified key. + /// Used in unit tests to verify that the cache contains the expected entries. /// /// /// diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj index 294a5c02b2..51af5632e3 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj +++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.csproj @@ -15,8 +15,10 @@ false MIT true + true + true - + true $(SigningKeyPath) @@ -30,16 +32,16 @@ - + - + - - - - + + + + diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/SqlColumnEncryptionAzureKeyVaultProvider.cs b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/SqlColumnEncryptionAzureKeyVaultProvider.cs index eb8c8d77c4..d4740a1183 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/SqlColumnEncryptionAzureKeyVaultProvider.cs +++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/SqlColumnEncryptionAzureKeyVaultProvider.cs @@ -4,6 +4,7 @@ using System; using System.Text; +using System.Threading; using Azure.Core; using Azure.Security.KeyVault.Keys.Cryptography; using static Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.Validator; @@ -55,6 +56,8 @@ public class SqlColumnEncryptionAzureKeyVaultProvider : SqlColumnEncryptionKeySt private readonly static KeyWrapAlgorithm s_keyWrapAlgorithm = KeyWrapAlgorithm.RsaOaep; + private SemaphoreSlim _cacheSemaphore = new(1, 1); + /// /// List of Trusted Endpoints /// @@ -69,7 +72,7 @@ public class SqlColumnEncryptionAzureKeyVaultProvider : SqlColumnEncryptionKeySt /// /// A cache for storing the results of signature verification of column master key metadata. /// - private readonly LocalCache, bool> _columnMasterKeyMetadataSignatureVerificationCache = + private readonly LocalCache, bool> _columnMasterKeyMetadataSignatureVerificationCache = new(maxSizeLimit: 2000) { TimeToLive = TimeSpan.FromDays(10) }; /// @@ -230,7 +233,7 @@ byte[] DecryptEncryptionKey() // Get ciphertext byte[] cipherText = new byte[cipherTextLength]; Array.Copy(encryptedColumnEncryptionKey, currentIndex, cipherText, 0, cipherTextLength); - + currentIndex += cipherTextLength; // Get signature @@ -394,17 +397,10 @@ private byte[] CompileMasterKeyMetadata(string masterKeyPath, bool allowEnclaveC /// An array of bytes to convert. /// A string of hexadecimal characters /// - /// Produces a string of hexadecimal character pairs preceded with "0x", where each pair represents the corresponding element in value; for example, "0x7F2C4A00". + /// Produces a string of hexadecimal character pairs preceded with "0x", where each pair represents the corresponding element in source; for example, "0x7F2C4A00". /// private string ToHexString(byte[] source) - { - if (source is null) - { - return null; - } - - return "0x" + BitConverter.ToString(source).Replace("-", ""); - } + => source is null ? null : "0x" + BitConverter.ToString(source).Replace("-", ""); /// /// Returns the cached decrypted column encryption key, or unwraps the encrypted column encryption key if not present. @@ -415,8 +411,21 @@ private string ToHexString(byte[] source) /// /// /// - private byte[] GetOrCreateColumnEncryptionKey(string encryptedColumnEncryptionKey, Func createItem) - => _columnEncryptionKeyCache.GetOrCreate(encryptedColumnEncryptionKey, createItem); + private byte[] GetOrCreateColumnEncryptionKey(string encryptedColumnEncryptionKey, Func createItem) + { + // Allow only one thread to access the cache at a time. + _cacheSemaphore.Wait(); + + try + { + return _columnEncryptionKeyCache.GetOrCreate(encryptedColumnEncryptionKey, createItem); + } + finally + { + // Release the semaphore to allow other threads to access the cache. + _cacheSemaphore.Release(); + } + } /// /// Returns the cached signature verification result, or proceeds to verify if not present. diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.Designer.cs index fc5d88930a..c3b9ce9104 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.Designer.cs @@ -8,6 +8,8 @@ // //------------------------------------------------------------------------------ +using System.Globalization; + namespace Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider { /// @@ -88,6 +90,17 @@ internal static string EmptyArgumentInternal } } + /// + /// Looks up a localized string similar to: Failed to fetch key from Azure Key Vault. Key: {0}. + /// + internal static string GetKeyFailed + { + get + { + return ResourceManager.GetString("GetKeyFailed", resourceCulture); + } + } + /// /// Looks up a localized string similar to Signed hash length does not match the RSA key size.. /// @@ -199,7 +212,18 @@ internal static string InvalidSignatureTemplate } /// - /// Looks up a localized string similar to Invalid trusted endpoint specified: '{0}'; a trusted endpoint must have a value.. + /// Looks up a localized string similar to The key with identifier '{0}' was not found.. + /// + internal static string MasterKeyNotFound + { + get + { + return ResourceManager.GetString("MasterKeyNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to One or more of the elements in '{0}' are null or empty or consist of only whitespace.. /// internal static string NullOrWhitespaceForEach { diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.resx b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.resx index 039d1079d5..8775b345ab 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.resx +++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Strings.resx @@ -118,13 +118,16 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - One or more of the elements in {0} are null or empty or consist of only whitespace. + One or more of the elements in '{0}' are null or empty or consist of only whitespace. CipherText length does not match the RSA key size. - Internal error. Empty {0} specified. + Internal error. Empty '{0}' specified. + + + Failed to fetch key from Azure Key Vault. Key: {0}. The key with identifier '{0}' was not found. diff --git a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Utils.cs b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Utils.cs index f71080ffab..0eb4dc1c9b 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Utils.cs +++ b/src/Microsoft.Data.SqlClient/add-ons/AzureKeyVaultProvider/Utils.cs @@ -86,7 +86,10 @@ internal static ArgumentException NullOrWhitespaceForEach(string name) => new(string.Format(Strings.NullOrWhitespaceForEach, name)); internal static KeyNotFoundException MasterKeyNotFound(string masterKeyPath) => - new(string.Format(CultureInfo.InvariantCulture, Strings.InvalidSignatureTemplate, masterKeyPath)); + new(string.Format(CultureInfo.InvariantCulture, Strings.MasterKeyNotFound, masterKeyPath)); + + internal static KeyNotFoundException GetKeyFailed(string masterKeyPath) => + new(string.Format(CultureInfo.InvariantCulture, Strings.GetKeyFailed, masterKeyPath)); internal static FormatException NonRsaKeyFormat(string keyType) => new(string.Format(CultureInfo.InvariantCulture, Strings.NonRsaKeyTemplate, keyType)); diff --git a/src/Microsoft.Data.SqlClient/add-ons/Directory.Build.props b/src/Microsoft.Data.SqlClient/add-ons/Directory.Build.props index aa51d600f1..7776439adc 100644 --- a/src/Microsoft.Data.SqlClient/add-ons/Directory.Build.props +++ b/src/Microsoft.Data.SqlClient/add-ons/Directory.Build.props @@ -17,7 +17,7 @@ net462 - net8.0 + net8.0;net9.0 @@ -34,7 +34,7 @@ - net8.0 + net8.0;net9.0 net462 diff --git a/src/Microsoft.Data.SqlClient/add-ons/Directory.Packages.props b/src/Microsoft.Data.SqlClient/add-ons/Directory.Packages.props new file mode 100644 index 0000000000..4a24bb25ae --- /dev/null +++ b/src/Microsoft.Data.SqlClient/add-ons/Directory.Packages.props @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs index cefabcb537..a07133eb07 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -5,11 +5,6 @@ // NOTE: The current Microsoft.VSDesigner editor attributes are implemented for System.Data.SqlClient, and are not publicly available. // New attributes that are designed to work with Microsoft.Data.SqlClient and are publicly documented should be included in future. -using System; -#if NET -using System.Diagnostics.CodeAnalysis; -#endif - [assembly: System.CLSCompliant(true)] namespace Microsoft.Data { @@ -24,6 +19,8 @@ public static class SqlDbTypeExtensions { /// public const System.Data.SqlDbType Json = (System.Data.SqlDbType)35; + /// + public const System.Data.SqlDbType Vector = (System.Data.SqlDbType)36; } } @@ -116,13 +113,31 @@ public SqlJson(string jsonString) { } /// public SqlJson(System.Text.Json.JsonDocument jsonDoc) { } /// - public bool IsNull => throw null; + public bool IsNull => throw null; /// public static SqlJson Null => throw null; /// public string Value { get { throw null; } } - /// - virtual public SqlJson GetSqlJson(int i) { throw null; } + /// + public override string ToString() { throw null; } + } + + /// + public readonly struct SqlVector : System.Data.SqlTypes.INullable + where T : unmanaged + { + /// + public SqlVector(System.ReadOnlyMemory memory) { } + /// + public bool IsNull => throw null; + /// + public static SqlVector? Null => throw null; + /// + public int Length { get { throw null; } } + /// + public System.ReadOnlyMemory Memory { get { throw null; } } + /// + public static SqlVector CreateNull(int length) { throw null; } } } namespace Microsoft.Data.SqlClient @@ -461,12 +476,14 @@ internal SqlClientFactory() { } public override System.Data.Common.DbParameter CreateParameter() { throw null; } /// public override System.Data.Common.DbDataSourceEnumerator CreateDataSourceEnumerator() { throw null; } +#if NET /// public override bool CanCreateBatch { get { throw null; } } /// public override System.Data.Common.DbBatch CreateBatch() { throw null; } /// public override System.Data.Common.DbBatchCommand CreateBatchCommand() { throw null; } +#endif } /// public partial class SqlClientLogger @@ -534,7 +551,7 @@ public enum SqlConnectionAttestationProtocol /// HGS = 3 } - + /// public enum SqlConnectionIPAddressPreference { @@ -1059,6 +1076,7 @@ public SqlConnectionStringBuilder(string connectionString) { } /// [System.ComponentModel.DisplayNameAttribute("Data Source")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] + [System.ComponentModel.TypeConverter("Microsoft.Data.SqlClient.SqlConnectionStringBuilder+SqlDataSourceConverter, Microsoft.Data.SqlClient")] public string DataSource { get { throw null; } set { } } /// [System.ComponentModel.DisplayNameAttribute("Attestation Protocol")] @@ -1092,6 +1110,7 @@ public SqlConnectionStringBuilder(string connectionString) { } /// [System.ComponentModel.DisplayNameAttribute("Failover Partner")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] + [System.ComponentModel.TypeConverter("Microsoft.Data.SqlClient.SqlConnectionStringBuilder+SqlDataSourceConverter, Microsoft.Data.SqlClient")] public string FailoverPartner { get { throw null; } set { } } /// [System.ComponentModel.DisplayNameAttribute("Failover Partner SPN")] @@ -1253,7 +1272,7 @@ protected override void OnRowUpdating(System.Data.Common.RowUpdatingEventArgs va object System.ICloneable.Clone() { throw null; } } /// - public partial class SqlDataReader : System.Data.Common.DbDataReader, System.Data.IDataReader, System.IDisposable + public partial class SqlDataReader : System.Data.Common.DbDataReader, System.Data.IDataReader, System.Data.Common.IDbColumnSchemaGenerator, System.IDisposable { internal SqlDataReader() { } /// @@ -1273,7 +1292,7 @@ internal SqlDataReader() { } /// public override int RecordsAffected { get { throw null; } } /// - public Microsoft.Data.SqlClient.DataClassification.SensitivityClassification SensitivityClassification { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Data.SqlClient.DataClassification.SensitivityClassification SensitivityClassification { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } /// public override int VisibleFieldCount { get { throw null; } } /// @@ -1290,7 +1309,8 @@ public override void Close() { } /// public override long GetChars(int i, long dataIndex, char[] buffer, int bufferIndex, int length) { throw null; } /// - public System.Collections.ObjectModel.ReadOnlyCollection GetColumnSchema() { throw null; }/// + public System.Collections.ObjectModel.ReadOnlyCollection GetColumnSchema() { throw null; } + /// public override string GetDataTypeName(int i) { throw null; } /// public override System.DateTime GetDateTime(int i) { throw null; } @@ -1304,7 +1324,7 @@ public override void Close() { } public override System.Collections.IEnumerator GetEnumerator() { throw null; } /// #if NET - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] + [return: System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] #endif public override System.Type GetFieldType(int i) { throw null; } /// @@ -1327,7 +1347,7 @@ public override void Close() { } public override int GetOrdinal(string name) { throw null; } /// #if NET - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] + [return: System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] #endif public override System.Type GetProviderSpecificFieldType(int i) { throw null; } /// @@ -1358,6 +1378,8 @@ public override void Close() { } public virtual System.Data.SqlTypes.SqlInt32 GetSqlInt32(int i) { throw null; } /// public virtual System.Data.SqlTypes.SqlInt64 GetSqlInt64(int i) { throw null; } + /// + virtual public Microsoft.Data.SqlTypes.SqlJson GetSqlJson(int i) { throw null; } /// public virtual System.Data.SqlTypes.SqlMoney GetSqlMoney(int i) { throw null; } /// @@ -1368,6 +1390,8 @@ public override void Close() { } public virtual object GetSqlValue(int i) { throw null; } /// public virtual int GetSqlValues(object[] values) { throw null; } + /// + public virtual Microsoft.Data.SqlTypes.SqlVector GetSqlVector(int i) where T : unmanaged { throw null; } /// public virtual System.Data.SqlTypes.SqlXml GetSqlXml(int i) { throw null; } /// @@ -1497,7 +1521,9 @@ internal SqlException() { } public byte State { get { throw null; } } /// +#if NET [System.Obsolete("This API supports obsolete formatter-based serialization. It should not be called or extended by application code.", DiagnosticId = "SYSLIB0051", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] +#endif public override void GetObjectData(System.Runtime.Serialization.SerializationInfo si, System.Runtime.Serialization.StreamingContext context) { } /// public override string ToString() { throw null; } @@ -1637,7 +1663,7 @@ public SqlParameter(string parameterName, object value) { } [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] public override System.Data.ParameterDirection Direction { get { throw null; } set { } } /// - public bool ForceColumnEncryption { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public bool ForceColumnEncryption { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute] set { } } /// [System.ComponentModel.DefaultValueAttribute(false)] public override bool IsNullable { get { throw null; } set { } } @@ -1829,11 +1855,19 @@ public override void Commit() { } protected override void Dispose(bool disposing) { } /// public override void Rollback() { } +#if NET /// public override void Rollback(string transactionName) { } /// public override void Save(string savePointName) { } +#else + /// + public void Rollback(string transactionName) { } + + /// + public void Save(string savePointName) { } +#endif } /// public sealed class SqlRetryingEventArgs : System.EventArgs @@ -1945,58 +1979,43 @@ public sealed class SqlConfigurableRetryFactory } namespace Microsoft.Data.SqlClient.Diagnostics { - /// - public abstract class SqlClientDiagnostic : System.Collections.Generic.IReadOnlyList> - { - internal SqlClientDiagnostic() { } - /// - protected const int CommonPropertyCount = 3; - /// - protected SqlClientDiagnostic(System.Guid operationId, string operation, long timestamp) { } + /// + public sealed class SqlClientCommandBefore : System.Collections.Generic.IReadOnlyList> + { + /// + public const string Name = "Microsoft.Data.SqlClient.WriteCommandBefore"; /// public System.Guid OperationId => throw null; /// public string Operation => throw null; /// public long Timestamp => throw null; - /// > - public int Count => CommonPropertyCount + GetDerivedCount(); - /// > - public System.Collections.Generic.KeyValuePair this[int index] => throw null; - /// > - public System.Collections.Generic.IEnumerator> GetEnumerator() => throw null; - /// > - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); - /// - protected bool TryGetCommonProperty(int index, out System.Collections.Generic.KeyValuePair property) => throw null; - /// - protected abstract int GetDerivedCount(); - /// - protected abstract System.Collections.Generic.KeyValuePair GetDerivedProperty(int index); - } - - /// - public sealed class SqlClientCommandBefore : SqlClientDiagnostic - { - /// - public const string Name = "Microsoft.Data.SqlClient.WriteCommandBefore"; /// public System.Guid? ConnectionId => throw null; /// public long? TransactionId => throw null; /// public SqlCommand Command => throw null; - /// > - protected sealed override int GetDerivedCount() => 3; - /// > - protected sealed override System.Collections.Generic.KeyValuePair GetDerivedProperty(int index) => throw null; + /// + public int Count => throw null; + /// + public System.Collections.Generic.KeyValuePair this[int index] => throw null; + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public System.Collections.Generic.IEnumerator> GetEnumerator() => throw null; } - /// - public sealed class SqlClientCommandAfter : SqlClientDiagnostic + public sealed class SqlClientCommandAfter : System.Collections.Generic.IReadOnlyList> { /// public const string Name = "Microsoft.Data.SqlClient.WriteCommandAfter"; + /// + public System.Guid OperationId => throw null; + /// + public string Operation => throw null; + /// + public long Timestamp => throw null; /// public System.Guid? ConnectionId => throw null; /// @@ -2005,17 +2024,26 @@ public sealed class SqlClientCommandAfter : SqlClientDiagnostic public SqlCommand Command => throw null; /// public System.Collections.IDictionary Statistics => throw null; - /// > - protected sealed override int GetDerivedCount() => 4; - /// > - protected sealed override System.Collections.Generic.KeyValuePair GetDerivedProperty(int index) => throw null; + /// + public int Count => throw null; + /// + public System.Collections.Generic.KeyValuePair this[int index] => throw null; + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public System.Collections.Generic.IEnumerator> GetEnumerator() => throw null; } - /// - public sealed class SqlClientCommandError : SqlClientDiagnostic + public sealed class SqlClientCommandError : System.Collections.Generic.IReadOnlyList> { /// public const string Name = "Microsoft.Data.SqlClient.WriteCommandError"; + /// + public System.Guid OperationId => throw null; + /// + public string Operation => throw null; + /// + public long Timestamp => throw null; /// public System.Guid? ConnectionId => throw null; /// @@ -2024,32 +2052,50 @@ public sealed class SqlClientCommandError : SqlClientDiagnostic public SqlCommand Command => throw null; /// public System.Exception Exception { get; } - /// > - protected sealed override int GetDerivedCount() => 4; - /// > - protected sealed override System.Collections.Generic.KeyValuePair GetDerivedProperty(int index) => throw null; + /// + public int Count => throw null; + /// >/// + public System.Collections.Generic.KeyValuePair this[int index] => throw null; + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public System.Collections.Generic.IEnumerator> GetEnumerator() => throw null; } - /// - public sealed class SqlClientConnectionOpenBefore : SqlClientDiagnostic + public sealed class SqlClientConnectionOpenBefore : System.Collections.Generic.IReadOnlyList> { /// public const string Name = "Microsoft.Data.SqlClient.WriteConnectionOpenBefore"; + /// + public System.Guid OperationId => throw null; + /// + public string Operation => throw null; + /// + public long Timestamp => throw null; /// public SqlConnection Connection => throw null; /// public string ClientVersion => throw null; - /// > - protected override int GetDerivedCount() => 2; - /// > - protected sealed override System.Collections.Generic.KeyValuePair GetDerivedProperty(int index) => throw null; + /// + public int Count => throw null; + /// >/// + public System.Collections.Generic.KeyValuePair this[int index] => throw null; + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public System.Collections.Generic.IEnumerator> GetEnumerator() => throw null; } - /// - public sealed class SqlClientConnectionOpenAfter : SqlClientDiagnostic + public sealed class SqlClientConnectionOpenAfter : System.Collections.Generic.IReadOnlyList> { /// public const string Name = "Microsoft.Data.SqlClient.WriteConnectionOpenAfter"; + /// + public System.Guid OperationId => throw null; + /// + public string Operation => throw null; + /// + public long Timestamp => throw null; /// public System.Guid ConnectionId => throw null; /// @@ -2058,17 +2104,26 @@ public sealed class SqlClientConnectionOpenAfter : SqlClientDiagnostic public string ClientVersion => throw null; /// public System.Collections.IDictionary Statistics => throw null; - /// > - protected override int GetDerivedCount() => 4; - /// > - protected sealed override System.Collections.Generic.KeyValuePair GetDerivedProperty(int index) => throw null; + /// + public int Count => throw null; + /// >/// + public System.Collections.Generic.KeyValuePair this[int index] => throw null; + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public System.Collections.Generic.IEnumerator> GetEnumerator() => throw null; } - /// - public sealed class SqlClientConnectionOpenError : SqlClientDiagnostic + public sealed class SqlClientConnectionOpenError : System.Collections.Generic.IReadOnlyList> { /// public const string Name = "Microsoft.Data.SqlClient.WriteConnectionOpenError"; + /// + public System.Guid OperationId => throw null; + /// + public string Operation => throw null; + /// + public long Timestamp => throw null; /// public System.Guid ConnectionId => throw null; /// @@ -2077,56 +2132,78 @@ public sealed class SqlClientConnectionOpenError : SqlClientDiagnostic public string ClientVersion => throw null; /// public System.Exception Exception => throw null; - /// > - protected override int GetDerivedCount() => 4; - /// > - protected sealed override System.Collections.Generic.KeyValuePair GetDerivedProperty(int index) => throw null; + /// + public int Count => throw null; + /// >/// + public System.Collections.Generic.KeyValuePair this[int index] => throw null; + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public System.Collections.Generic.IEnumerator> GetEnumerator() => throw null; } - /// - public sealed class SqlClientConnectionCloseBefore : SqlClientDiagnostic + public sealed class SqlClientConnectionCloseBefore : System.Collections.Generic.IReadOnlyList> { /// public const string Name = "Microsoft.Data.SqlClient.WriteConnectionCloseBefore"; + /// + public System.Guid OperationId => throw null; + /// + public string Operation => throw null; + /// + public long Timestamp => throw null; /// public System.Guid? ConnectionId => throw null; /// public SqlConnection Connection => throw null; /// public System.Collections.IDictionary Statistics => throw null; - /// > - protected sealed override int GetDerivedCount() => 3; - /// > - protected sealed override System.Collections.Generic.KeyValuePair GetDerivedProperty(int index) => throw null; + /// + public int Count => throw null; + /// >/// + public System.Collections.Generic.KeyValuePair this[int index] => throw null; + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public System.Collections.Generic.IEnumerator> GetEnumerator() => throw null; } - /// - public sealed class SqlClientConnectionCloseAfter : SqlClientDiagnostic + public sealed class SqlClientConnectionCloseAfter : System.Collections.Generic.IReadOnlyList> { /// public const string Name = "Microsoft.Data.SqlClient.WriteConnectionCloseAfter"; - + /// + public System.Guid OperationId => throw null; + /// + public string Operation => throw null; + /// + public long Timestamp => throw null; /// public System.Guid? ConnectionId => throw null; - /// public SqlConnection Connection => throw null; - /// public System.Collections.IDictionary Statistics => throw null; - - /// > - protected sealed override int GetDerivedCount() => 3; - - /// > - protected sealed override System.Collections.Generic.KeyValuePair GetDerivedProperty(int index) => throw null; + /// + public int Count => throw null; + /// >/// + public System.Collections.Generic.KeyValuePair this[int index] => throw null; + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public System.Collections.Generic.IEnumerator> GetEnumerator() => throw null; } - /// - public sealed class SqlClientConnectionCloseError : SqlClientDiagnostic + public sealed class SqlClientConnectionCloseError : System.Collections.Generic.IReadOnlyList> { /// public const string Name = "Microsoft.Data.SqlClient.WriteConnectionCloseError"; + /// + public System.Guid OperationId => throw null; + /// + public string Operation => throw null; + /// + public long Timestamp => throw null; /// public System.Guid? ConnectionId => throw null; /// @@ -2135,51 +2212,78 @@ public sealed class SqlClientConnectionCloseError : SqlClientDiagnostic public System.Collections.IDictionary Statistics => throw null; /// public System.Exception Exception => throw null; - /// > - protected sealed override int GetDerivedCount() => 4; - /// > - protected sealed override System.Collections.Generic.KeyValuePair GetDerivedProperty(int index) => throw null; + /// + public int Count => throw null; + /// >/// + public System.Collections.Generic.KeyValuePair this[int index] => throw null; + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public System.Collections.Generic.IEnumerator> GetEnumerator() => throw null; } - /// - public sealed class SqlClientTransactionCommitBefore : SqlClientDiagnostic + public sealed class SqlClientTransactionCommitBefore : System.Collections.Generic.IReadOnlyList> { /// public const string Name = "Microsoft.Data.SqlClient.WriteTransactionCommitBefore"; + /// + public System.Guid OperationId => throw null; + /// + public string Operation => throw null; + /// + public long Timestamp => throw null; /// public System.Data.IsolationLevel IsolationLevel => throw null; /// public SqlConnection Connection => throw null; /// public long? TransactionId => throw null; - /// > - protected sealed override int GetDerivedCount() => 3; - /// > - protected sealed override System.Collections.Generic.KeyValuePair GetDerivedProperty(int index) => throw null; + /// + public int Count => throw null; + /// >/// + public System.Collections.Generic.KeyValuePair this[int index] => throw null; + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public System.Collections.Generic.IEnumerator> GetEnumerator() => throw null; } - /// - public sealed class SqlClientTransactionCommitAfter : SqlClientDiagnostic + public sealed class SqlClientTransactionCommitAfter : System.Collections.Generic.IReadOnlyList> { /// public const string Name = "Microsoft.Data.SqlClient.WriteTransactionCommitAfter"; + /// + public System.Guid OperationId => throw null; + /// + public string Operation => throw null; + /// + public long Timestamp => throw null; /// public System.Data.IsolationLevel IsolationLevel => throw null; /// public SqlConnection Connection => throw null; /// public long? TransactionId => throw null; - /// > - protected sealed override int GetDerivedCount() => 3; - /// > - protected sealed override System.Collections.Generic.KeyValuePair GetDerivedProperty(int index) => throw null; + /// + public int Count => throw null; + /// >/// + public System.Collections.Generic.KeyValuePair this[int index] => throw null; + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public System.Collections.Generic.IEnumerator> GetEnumerator() => throw null; } - /// - public sealed class SqlClientTransactionCommitError : SqlClientDiagnostic + public sealed class SqlClientTransactionCommitError : System.Collections.Generic.IReadOnlyList> { /// public const string Name = "Microsoft.Data.SqlClient.WriteTransactionCommitError"; + /// + public System.Guid OperationId => throw null; + /// + public string Operation => throw null; + /// + public long Timestamp => throw null; /// public System.Data.IsolationLevel IsolationLevel => throw null; /// @@ -2188,17 +2292,26 @@ public sealed class SqlClientTransactionCommitError : SqlClientDiagnostic public long? TransactionId => throw null; /// public System.Exception Exception => throw null; - /// > - protected sealed override int GetDerivedCount() => 4; - /// > - protected sealed override System.Collections.Generic.KeyValuePair GetDerivedProperty(int index) => throw null; + /// + public int Count => throw null; + /// >/// + public System.Collections.Generic.KeyValuePair this[int index] => throw null; + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public System.Collections.Generic.IEnumerator> GetEnumerator() => throw null; } - /// - public sealed class SqlClientTransactionRollbackBefore : SqlClientDiagnostic + public sealed class SqlClientTransactionRollbackBefore : System.Collections.Generic.IReadOnlyList> { /// public const string Name = "Microsoft.Data.SqlClient.WriteTransactionRollbackBefore"; + /// + public System.Guid OperationId => throw null; + /// + public string Operation => throw null; + /// + public long Timestamp => throw null; /// public System.Data.IsolationLevel IsolationLevel => throw null; /// @@ -2207,17 +2320,26 @@ public sealed class SqlClientTransactionRollbackBefore : SqlClientDiagnostic public long? TransactionId => throw null; /// public string TransactionName => throw null; - /// > - protected sealed override int GetDerivedCount() => 4; - /// > - protected sealed override System.Collections.Generic.KeyValuePair GetDerivedProperty(int index) => throw null; + /// + public int Count => throw null; + /// >/// + public System.Collections.Generic.KeyValuePair this[int index] => throw null; + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public System.Collections.Generic.IEnumerator> GetEnumerator() => throw null; } - /// - public sealed class SqlClientTransactionRollbackAfter : SqlClientDiagnostic + public sealed class SqlClientTransactionRollbackAfter : System.Collections.Generic.IReadOnlyList> { /// public const string Name = "Microsoft.Data.SqlClient.WriteTransactionRollbackAfter"; + /// + public System.Guid OperationId => throw null; + /// + public string Operation => throw null; + /// + public long Timestamp => throw null; /// public System.Data.IsolationLevel IsolationLevel => throw null; /// @@ -2226,17 +2348,26 @@ public sealed class SqlClientTransactionRollbackAfter : SqlClientDiagnostic public long? TransactionId => throw null; /// public string TransactionName => throw null; - /// > - protected sealed override int GetDerivedCount() => 4; - /// > - protected sealed override System.Collections.Generic.KeyValuePair GetDerivedProperty(int index) => throw null; + /// + public int Count => throw null; + /// >/// + public System.Collections.Generic.KeyValuePair this[int index] => throw null; + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public System.Collections.Generic.IEnumerator> GetEnumerator() => throw null; } - /// - public sealed class SqlClientTransactionRollbackError : SqlClientDiagnostic + public sealed class SqlClientTransactionRollbackError : System.Collections.Generic.IReadOnlyList> { /// public const string Name = "Microsoft.Data.SqlClient.WriteTransactionRollbackError"; + /// + public System.Guid OperationId => throw null; + /// + public string Operation => throw null; + /// + public long Timestamp => throw null; /// public System.Data.IsolationLevel IsolationLevel => throw null; /// @@ -2247,10 +2378,14 @@ public sealed class SqlClientTransactionRollbackError : SqlClientDiagnostic public string TransactionName => throw null; /// public System.Exception Exception => throw null; - /// > - protected sealed override int GetDerivedCount() => 5; - /// > - protected sealed override System.Collections.Generic.KeyValuePair GetDerivedProperty(int index) => throw null; + /// + public int Count => throw null; + /// >/// + public System.Collections.Generic.KeyValuePair this[int index] => throw null; + /// + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + /// + public System.Collections.Generic.IEnumerator> GetEnumerator() => throw null; } } namespace Microsoft.Data.SqlClient.Server @@ -2290,7 +2425,7 @@ public SqlDataRecord(params Microsoft.Data.SqlClient.Server.SqlMetaData[] metaDa public virtual double GetDouble(int ordinal) { throw null; } /// #if NET - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicProperties)] + [return: System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicFields | System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties)] #endif public virtual System.Type GetFieldType(int ordinal) { throw null; } /// @@ -2575,7 +2710,7 @@ public partial class ColumnSensitivity /// public ColumnSensitivity(System.Collections.Generic.IList sensitivityProperties) { } /// - public System.Collections.ObjectModel.ReadOnlyCollection SensitivityProperties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.ObjectModel.ReadOnlyCollection SensitivityProperties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } /// public partial class InformationType @@ -2583,9 +2718,9 @@ public partial class InformationType /// public InformationType(string name, string id) { } /// - public string Id { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Id { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } /// - public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } /// public partial class Label @@ -2593,9 +2728,9 @@ public partial class Label /// public Label(string name, string id) { } /// - public string Id { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Id { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } /// - public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public string Name { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } } /// public enum SensitivityRank @@ -2619,11 +2754,11 @@ public partial class SensitivityClassification /// public SensitivityClassification(System.Collections.Generic.IList labels, System.Collections.Generic.IList informationTypes, System.Collections.Generic.IList columnSensitivity, SensitivityRank sensitivityRank) { } /// - public System.Collections.ObjectModel.ReadOnlyCollection ColumnSensitivities { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.ObjectModel.ReadOnlyCollection ColumnSensitivities { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } /// - public System.Collections.ObjectModel.ReadOnlyCollection InformationTypes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.ObjectModel.ReadOnlyCollection InformationTypes { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } /// - public System.Collections.ObjectModel.ReadOnlyCollection Labels { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public System.Collections.ObjectModel.ReadOnlyCollection Labels { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } /// public SensitivityRank SensitivityRank { get { throw null; } } } @@ -2633,9 +2768,9 @@ public partial class SensitivityProperty /// public SensitivityProperty(Microsoft.Data.SqlClient.DataClassification.Label label, Microsoft.Data.SqlClient.DataClassification.InformationType informationType, SensitivityRank sensitivityRank) { } /// - public Microsoft.Data.SqlClient.DataClassification.InformationType InformationType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Data.SqlClient.DataClassification.InformationType InformationType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } /// - public Microsoft.Data.SqlClient.DataClassification.Label Label { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } + public Microsoft.Data.SqlClient.DataClassification.Label Label { [System.Runtime.CompilerServices.CompilerGeneratedAttribute] get { throw null; } } /// public SensitivityRank SensitivityRank { get { throw null; } } } 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 5a3737e6f4..0944a4aea0 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -1,17 +1,20 @@  false - net8.0;net9.0 + net8.0;net9.0;netstandard2.0 $(ObjFolder)$(Configuration)\$(AssemblyName)\ref\ + netcoreapp $(BinFolder)$(Configuration)\$(AssemblyName)\ref\ + + $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName)\netstandard\ $(OutputPath)\$(TargetFramework)\Microsoft.Data.SqlClient.xml Core $(BaseProduct) Debug;Release; - netcoreapp AnyCPU;x64;x86 - - + + true $(SigningKeyPath) $(SigningKeyPath) @@ -19,20 +22,33 @@ $(SigningKeyPath) - + + + - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Common/System/NotImplemented.cs b/src/Microsoft.Data.SqlClient/netcore/src/Common/System/NotImplemented.cs deleted file mode 100644 index fa0fe07fa7..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Common/System/NotImplemented.cs +++ /dev/null @@ -1,31 +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. - -namespace System -{ - // - // Support for tooling-friendly NotImplementedExceptions. - // - internal static class NotImplemented - { - /// - /// Permanent NotImplementedException with no message shown to user. - /// - internal static Exception ByDesign => new NotImplementedException(); - - /// - /// Permanent NotImplementedException with localized message shown to user. - /// - internal static Exception ByDesignWithMessage(string message) - { - return new NotImplementedException(message); - } - - /// - /// Temporary NotImplementedException with no message shown to user. - /// Example: Exception.ActiveIssue("https://github.com/dotnet/corefx/issues/xxxx") or Exception.ActiveIssue("TFS xxxxxx"). - /// - internal static Exception ActiveIssue(string issue) => new NotImplementedException(); - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Common/System/Threading/Tasks/TaskToApm.cs b/src/Microsoft.Data.SqlClient/netcore/src/Common/System/Threading/Tasks/TaskToApm.cs deleted file mode 100644 index add41f588e..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Common/System/Threading/Tasks/TaskToApm.cs +++ /dev/null @@ -1,189 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -// Helper methods for using Tasks to implement the APM pattern. -// -// Example usage, wrapping a Task-returning FooAsync method with Begin/EndFoo methods: -// -// public IAsyncResult BeginFoo(..., AsyncCallback callback, object state) -// { -// Task t = FooAsync(...); -// return TaskToApm.Begin(t, callback, state); -// } -// public int EndFoo(IAsyncResult asyncResult) -// { -// return TaskToApm.End(asyncResult); -// } - -using System.Diagnostics; - -namespace System.Threading.Tasks -{ - /// - /// Provides support for efficiently using Tasks to implement the APM (Begin/End) pattern. - /// - internal static class TaskToApm - { - /// - /// Marshals the Task as an IAsyncResult, using the supplied callback and state - /// to implement the APM pattern. - /// - /// The Task to be marshaled. - /// The callback to be invoked upon completion. - /// The state to be stored in the IAsyncResult. - /// An IAsyncResult to represent the task's asynchronous operation. - public static IAsyncResult Begin(Task task, AsyncCallback callback, object state) - { - Debug.Assert(task != null); - - // If the task has already completed, then since the Task's CompletedSynchronously==false - // and we want it to be true, we need to create a new IAsyncResult. (We also need the AsyncState to match.) - IAsyncResult asyncResult; - if (task.IsCompleted) - { - // Synchronous completion. - asyncResult = new TaskWrapperAsyncResult(task, state, completedSynchronously: true); - callback?.Invoke(asyncResult); - } - else - { - // For asynchronous completion we need to schedule a callback. Whether we can use the Task as the IAsyncResult - // depends on whether the Task's AsyncState has reference equality with the requested state. - asyncResult = task.AsyncState == state ? (IAsyncResult)task : new TaskWrapperAsyncResult(task, state, completedSynchronously: false); - if (callback != null) - { - InvokeCallbackWhenTaskCompletes(task, callback, asyncResult); - } - } - return asyncResult; - } - - /// Processes an IAsyncResult returned by Begin. - /// The IAsyncResult to unwrap. - public static void End(IAsyncResult asyncResult) - { - Task task; - - // If the IAsyncResult is our task-wrapping IAsyncResult, extract the Task. - var twar = asyncResult as TaskWrapperAsyncResult; - if (twar != null) - { - task = twar.Task; - Debug.Assert(task != null, "TaskWrapperAsyncResult should never wrap a null Task."); - } - else - { - // Otherwise, the IAsyncResult should be a Task. - task = asyncResult as Task; - } - - // Make sure we actually got a task, then complete the operation by waiting on it. - if (task == null) - { - throw new ArgumentNullException(); - } - - task.GetAwaiter().GetResult(); - } - - /// Processes an IAsyncResult returned by Begin. - /// The IAsyncResult to unwrap. - public static TResult End(IAsyncResult asyncResult) - { - Task task; - - // If the IAsyncResult is our task-wrapping IAsyncResult, extract the Task. - var twar = asyncResult as TaskWrapperAsyncResult; - if (twar != null) - { - task = twar.Task as Task; - Debug.Assert(twar.Task != null, "TaskWrapperAsyncResult should never wrap a null Task."); - } - else - { - // Otherwise, the IAsyncResult should be a Task. - task = asyncResult as Task; - } - - // Make sure we actually got a task, then complete the operation by waiting on it. - if (task == null) - { - throw new ArgumentNullException(); - } - - return task.GetAwaiter().GetResult(); - } - - /// Invokes the callback asynchronously when the task has completed. - /// The Task to await. - /// The callback to invoke when the Task completes. - /// The Task used as the IAsyncResult. - private static void InvokeCallbackWhenTaskCompletes(Task antecedent, AsyncCallback callback, IAsyncResult asyncResult) - { - Debug.Assert(antecedent != null); - Debug.Assert(callback != null); - Debug.Assert(asyncResult != null); - - // We use OnCompleted rather than ContinueWith in order to avoid running synchronously - // if the task has already completed by the time we get here. This is separated out into - // its own method currently so that we only pay for the closure if necessary. - antecedent.ConfigureAwait(continueOnCapturedContext: false) - .GetAwaiter() - .OnCompleted(() => callback(asyncResult)); - - // PERFORMANCE NOTE: - // Assuming we're in the default ExecutionContext, the "slow path" of an incomplete - // task will result in four allocations: the new IAsyncResult, the delegate+closure - // in this method, and the continuation object inside of OnCompleted (necessary - // to capture both the Action delegate and the ExecutionContext in a single object). - // In the future, if performance requirements drove a need, those four - // allocations could be reduced to one. This would be achieved by having TaskWrapperAsyncResult - // also implement ITaskCompletionAction (and optionally IThreadPoolWorkItem). It would need - // additional fields to store the AsyncCallback and an ExecutionContext. Once configured, - // it would be set into the Task as a continuation. Its Invoke method would then be run when - // the antecedent completed, and, doing all of the necessary work to flow ExecutionContext, - // it would invoke the AsyncCallback. It could also have a field on it for the antecedent, - // so that the End method would have access to the completed antecedent. For related examples, - // see other implementations of ITaskCompletionAction, and in particular ReadWriteTask - // used in Stream.Begin/EndXx's implementation. - } - - /// - /// Provides a simple IAsyncResult that wraps a Task. This, in effect, allows - /// for overriding what's seen for the CompletedSynchronously and AsyncState values. - /// - private sealed class TaskWrapperAsyncResult : IAsyncResult - { - /// The wrapped Task. - internal readonly Task Task; - /// The new AsyncState value. - private readonly object _state; - /// The new CompletedSynchronously value. - private readonly bool _completedSynchronously; - - /// Initializes the IAsyncResult with the Task to wrap and the overriding AsyncState and CompletedSynchronously values. - /// The Task to wrap. - /// The new AsyncState value - /// The new CompletedSynchronously value. - internal TaskWrapperAsyncResult(Task task, object state, bool completedSynchronously) - { - Debug.Assert(task != null); - Debug.Assert(!completedSynchronously || task.IsCompleted, "If completedSynchronously is true, the task must be completed."); - - this.Task = task; - _state = state; - _completedSynchronously = completedSynchronously; - } - - // The IAsyncResult implementation. - // - IsCompleted and AsyncWaitHandle just pass through to the Task. - // - AsyncState and CompletedSynchronously return the corresponding values stored in this object. - - object IAsyncResult.AsyncState { get { return _state; } } - bool IAsyncResult.CompletedSynchronously { get { return _completedSynchronously; } } - bool IAsyncResult.IsCompleted { get { return this.Task.IsCompleted; } } - WaitHandle IAsyncResult.AsyncWaitHandle { get { return ((IAsyncResult)this.Task).AsyncWaitHandle; } } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Unix.cs b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Unix.cs deleted file mode 100644 index 3b512f0427..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Unix.cs +++ /dev/null @@ -1,27 +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; - -namespace Microsoft.Data -{ - internal static partial class SafeNativeMethods - { - internal static IntPtr GetProcAddress(IntPtr HModule, string funcName) - { - throw new PlatformNotSupportedException("SafeNativeMethods.GetProcAddress is not supported on non-Windows platforms"); - } - } -} - -namespace Microsoft.Data -{ - internal static class Win32NativeMethods - { - internal static bool IsTokenRestrictedWrapper(IntPtr token) - { - throw new PlatformNotSupportedException("Win32NativeMethods.IsTokenRestrictedWrapper is not supported on non-Windows platforms"); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs deleted file mode 100644 index 41708b7d8f..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Interop/SNINativeMethodWrapper.Windows.cs +++ /dev/null @@ -1,359 +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.Runtime.InteropServices; -using System.Text; -using Interop.Windows.Sni; -using Microsoft.Data.Common; -using Microsoft.Data.SqlClient; - -namespace Microsoft.Data.SqlClient -{ - internal static partial class SNINativeMethodWrapper - { - private const string SNI = "Microsoft.Data.SqlClient.SNI.dll"; - - private static int s_sniMaxComposedSpnLength = -1; - - private const int SniOpenTimeOut = -1; // infinite - - internal const int SniIP6AddrStringBufferLength = 48; // from SNI layer - - internal static int SniMaxComposedSpnLength - { - get - { - if (s_sniMaxComposedSpnLength == -1) - { - s_sniMaxComposedSpnLength = checked((int)GetSniMaxComposedSpnLength()); - } - return s_sniMaxComposedSpnLength; - } - } - - #region DLL Imports - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIAddProviderWrapper")] - internal static extern uint SNIAddProvider(SNIHandle pConn, Provider ProvNum, [In] ref uint pInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIAddProviderWrapper")] - internal static extern uint SNIAddProvider(SNIHandle pConn, Provider ProvNum, [In] ref AuthProviderInfo pInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNICheckConnectionWrapper")] - internal static extern uint SNICheckConnection([In] SNIHandle pConn); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNICloseWrapper")] - internal static extern uint SNIClose(IntPtr pConn); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern void SNIGetLastError(out SniError pErrorStruct); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern void SNIPacketRelease(IntPtr pPacket); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIPacketResetWrapper")] - internal static extern void SNIPacketReset([In] SNIHandle pConn, IoType IOType, SNIPacket pPacket, ConsumerNumber ConsNum); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIQueryInfo(QueryType QType, ref uint pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIQueryInfo(QueryType QType, ref IntPtr pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIReadAsyncWrapper")] - internal static extern uint SNIReadAsync(SNIHandle pConn, ref IntPtr ppNewPacket); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIReadSyncOverAsync(SNIHandle pConn, ref IntPtr ppNewPacket, int timeout); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIRemoveProviderWrapper")] - internal static extern uint SNIRemoveProvider(SNIHandle pConn, Provider ProvNum); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNISecInitPackage(ref uint pcbMaxToken); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNISetInfoWrapper")] - internal static extern uint SNISetInfo(SNIHandle pConn, QueryType QType, [In] ref uint pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNITerminate(); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIWaitForSSLHandshakeToCompleteWrapper")] - internal static extern uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint UnmanagedIsTokenRestricted([In] IntPtr token, [MarshalAs(UnmanagedType.Bool)] out bool isRestricted); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - private static extern uint GetSniMaxComposedSpnLength(); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, out Guid pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, out ushort portNum); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - private static extern uint SNIGetPeerAddrStrWrapper([In] SNIHandle pConn, int bufferSize, StringBuilder addrBuffer, out uint addrLen); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, out Provider provNum); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - private static extern uint SNIInitialize([In] IntPtr pmo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - private static extern uint SNIOpenSyncExWrapper(ref SniClientConsumerInfo pClientConsumerInfo, out IntPtr ppConn); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - private static extern uint SNIOpenWrapper( - [In] ref SniConsumerInfo pConsumerInfo, - [MarshalAs(UnmanagedType.LPWStr)] string szConnect, - [In] SNIHandle pConn, - out IntPtr ppConn, - [MarshalAs(UnmanagedType.Bool)] bool fSync, - SqlConnectionIPAddressPreference ipPreference, - [In] ref SniDnsCacheInfo pDNSCachedInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr SNIPacketAllocateWrapper([In] SafeHandle pConn, IoType IOType); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - private static extern uint SNIPacketGetDataWrapper([In] IntPtr packet, [In, Out] byte[] readBuffer, uint readBufferLength, out uint dataSize); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - private static extern unsafe void SNIPacketSetData(SNIPacket pPacket, [In] byte* pbBuf, uint cbBuf); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - private static extern unsafe uint SNISecGenClientContextWrapper( - [In] SNIHandle pConn, - [In, Out] byte* pIn, - uint cbIn, - [In, Out] byte[] pOut, - [In] ref uint pcbOut, - [MarshalAsAttribute(UnmanagedType.Bool)] out bool pfDone, - byte* szServerInfo, - uint cbServerInfo, - [MarshalAsAttribute(UnmanagedType.LPWStr)] string pwszUserName, - [MarshalAsAttribute(UnmanagedType.LPWStr)] string pwszPassword); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - private static extern uint SNIWriteAsyncWrapper(SNIHandle pConn, [In] SNIPacket pPacket); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - private static extern uint SNIWriteSyncOverAsync(SNIHandle pConn, [In] SNIPacket pPacket); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumOpenWrapper")] - internal static extern IntPtr SNIServerEnumOpen(); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumCloseWrapper")] - internal static extern void SNIServerEnumClose([In] IntPtr packet); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumReadWrapper", CharSet = CharSet.Unicode)] - internal static extern int SNIServerEnumRead([In] IntPtr packet, - [In][MarshalAs(UnmanagedType.LPArray)] char[] readBuffer, - [In] int bufferLength, - [MarshalAs(UnmanagedType.Bool)] out bool more); - #endregion - - internal static uint SniGetConnectionId(SNIHandle pConn, ref Guid connId) - { - return SNIGetInfoWrapper(pConn, QueryType.SNI_QUERY_CONN_CONNID, out connId); - } - - internal static uint SniGetProviderNumber(SNIHandle pConn, ref Provider provNum) - { - return SNIGetInfoWrapper(pConn, QueryType.SNI_QUERY_CONN_PROVIDERNUM, out provNum); - } - - internal static uint SniGetConnectionPort(SNIHandle pConn, ref ushort portNum) - { - return SNIGetInfoWrapper(pConn, QueryType.SNI_QUERY_CONN_PEERPORT, out portNum); - } - - internal static uint SniGetConnectionIPString(SNIHandle pConn, ref string connIPStr) - { - UInt32 ret; - uint connIPLen = 0; - - int bufferSize = SniIP6AddrStringBufferLength; - StringBuilder addrBuffer = new StringBuilder(bufferSize); - - ret = SNIGetPeerAddrStrWrapper(pConn, bufferSize, addrBuffer, out connIPLen); - - connIPStr = addrBuffer.ToString(0, Convert.ToInt32(connIPLen)); - - return ret; - } - - internal static uint SNIInitialize() - { - return SNIInitialize(IntPtr.Zero); - } - - internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHandle parent, ref IntPtr pConn, bool fSync, SqlConnectionIPAddressPreference ipPreference, SQLDNSInfo cachedDNSInfo) - { - // initialize consumer info for MARS - SniConsumerInfo native_consumerInfo = new SniConsumerInfo(); - MarshalConsumerInfo(consumerInfo, ref native_consumerInfo); - - SniDnsCacheInfo native_cachedDNSInfo = new SniDnsCacheInfo(); - native_cachedDNSInfo.wszCachedFQDN = cachedDNSInfo?.FQDN; - native_cachedDNSInfo.wszCachedTcpIPv4 = cachedDNSInfo?.AddrIPv4; - native_cachedDNSInfo.wszCachedTcpIPv6 = cachedDNSInfo?.AddrIPv6; - native_cachedDNSInfo.wszCachedTcpPort = cachedDNSInfo?.Port; - - return SNIOpenWrapper(ref native_consumerInfo, "session:", parent, out pConn, fSync, ipPreference, ref native_cachedDNSInfo); - } - - internal static unsafe uint SNIOpenSyncEx( - ConsumerInfo consumerInfo, - string constring, - ref IntPtr pConn, - byte[] spnBuffer, - byte[] instanceName, - bool fOverrideCache, - bool fSync, - int timeout, - bool fParallel, - SqlConnectionIPAddressPreference ipPreference, - SQLDNSInfo cachedDNSInfo, - string hostNameInCertificate) - { - - fixed (byte* pin_instanceName = &instanceName[0]) - { - SniClientConsumerInfo clientConsumerInfo = new SniClientConsumerInfo(); - - // initialize client ConsumerInfo part first - MarshalConsumerInfo(consumerInfo, ref clientConsumerInfo.ConsumerInfo); - - clientConsumerInfo.wszConnectionString = constring; - clientConsumerInfo.HostNameInCertificate = hostNameInCertificate; - clientConsumerInfo.networkLibrary = Prefix.UNKNOWN_PREFIX; - clientConsumerInfo.szInstanceName = pin_instanceName; - clientConsumerInfo.cchInstanceName = (uint)instanceName.Length; - clientConsumerInfo.fOverrideLastConnectCache = fOverrideCache; - clientConsumerInfo.fSynchronousConnection = fSync; - clientConsumerInfo.timeout = timeout; - clientConsumerInfo.fParallel = fParallel; - - clientConsumerInfo.transparentNetworkResolution = TransparentNetworkResolutionMode.DisabledMode; - clientConsumerInfo.totalTimeout = SniOpenTimeOut; - clientConsumerInfo.isAzureSqlServerEndpoint = ADP.IsAzureSqlServerEndpoint(constring); - - clientConsumerInfo.ipAddressPreference = ipPreference; - clientConsumerInfo.DNSCacheInfo.wszCachedFQDN = cachedDNSInfo?.FQDN; - clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv4 = cachedDNSInfo?.AddrIPv4; - clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv6 = cachedDNSInfo?.AddrIPv6; - clientConsumerInfo.DNSCacheInfo.wszCachedTcpPort = cachedDNSInfo?.Port; - - if (spnBuffer != null) - { - fixed (byte* pin_spnBuffer = &spnBuffer[0]) - { - clientConsumerInfo.szSPN = pin_spnBuffer; - clientConsumerInfo.cchSPN = (uint)spnBuffer.Length; - return SNIOpenSyncExWrapper(ref clientConsumerInfo, out pConn); - } - } - else - { - // else leave szSPN null (SQL Auth) - return SNIOpenSyncExWrapper(ref clientConsumerInfo, out pConn); - } - } - } - - internal static void SNIPacketAllocate(SafeHandle pConn, IoType IOType, ref IntPtr pPacket) - { - pPacket = SNIPacketAllocateWrapper(pConn, IOType); - } - - internal static unsafe uint SNIPacketGetData(IntPtr packet, byte[] readBuffer, ref uint dataSize) - { - return SNIPacketGetDataWrapper(packet, readBuffer, (uint)readBuffer.Length, out dataSize); - } - - internal static unsafe void SNIPacketSetData(SNIPacket packet, byte[] data, int length) - { - fixed (byte* pin_data = &data[0]) - { - SNIPacketSetData(packet, pin_data, (uint)length); - } - } - - internal static unsafe uint SNISecGenClientContext(SNIHandle pConnectionObject, ReadOnlySpan inBuff, byte[] OutBuff, ref uint sendLength, byte[] serverUserName) - { - fixed (byte* pin_serverUserName = &serverUserName[0]) - fixed (byte* pInBuff = inBuff) - { - return SNISecGenClientContextWrapper( - pConnectionObject, - pInBuff, - (uint)inBuff.Length, - OutBuff, - ref sendLength, - out _, - pin_serverUserName, - (uint)serverUserName.Length, - null, - null); - } - } - - internal static uint SNIWritePacket(SNIHandle pConn, SNIPacket packet, bool sync) - { - if (sync) - { - return SNIWriteSyncOverAsync(pConn, packet); - } - else - { - return SNIWriteAsyncWrapper(pConn, packet); - } - } - - private static void MarshalConsumerInfo(ConsumerInfo consumerInfo, ref SniConsumerInfo native_consumerInfo) - { - native_consumerInfo.DefaultUserDataLength = consumerInfo.defaultBufferSize; - native_consumerInfo.fnReadComp = consumerInfo.readDelegate != null - ? Marshal.GetFunctionPointerForDelegate(consumerInfo.readDelegate) - : IntPtr.Zero; - native_consumerInfo.fnWriteComp = consumerInfo.writeDelegate != null - ? Marshal.GetFunctionPointerForDelegate(consumerInfo.writeDelegate) - : IntPtr.Zero; - native_consumerInfo.ConsumerKey = consumerInfo.key; - } - } -} - -namespace Microsoft.Data -{ - internal static partial class SafeNativeMethods - { - [DllImport("kernel32.dll", CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)] - internal static extern IntPtr GetProcAddress(IntPtr HModule, [MarshalAs(UnmanagedType.LPStr), In] string funcName); - } -} - -namespace Microsoft.Data -{ - internal static class Win32NativeMethods - { - internal static bool IsTokenRestrictedWrapper(IntPtr token) - { - bool isRestricted; - uint result = SNINativeMethodWrapper.UnmanagedIsTokenRestricted(token, out isRestricted); - - if (result != 0) - { - Marshal.ThrowExceptionForHR(unchecked((int)result)); - } - - return isRestricted; - } - } -} 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 56843f73e1..9152859367 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -17,8 +17,9 @@ true Core $(BaseProduct) true - $(NoWarn);IL2026;IL2057;IL2072;IL2075 + $(NoWarn);IL2026;IL2057;IL2072;IL2075 + false @@ -32,10 +33,13 @@ $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFramework)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) + + + - + @@ -50,14 +54,32 @@ Microsoft\Data\Common\BitConverterCompatible.cs - - Microsoft\Data\Common\DbConnectionOptions.Common.cs + + Microsoft\Data\Common\ConnectionString\AttestationProtocolUtilities.cs + + + Microsoft\Data\Common\ConnectionString\DbConnectionOptions.cs + + + Microsoft\Data\Common\ConnectionString\DbConnectionOptions.Debug.cs + + + Microsoft\Data\Common\ConnectionString\DbConnectionStringDefaults.cs + + + Microsoft\Data\Common\ConnectionString\DbConnectionStringKeywords.cs + + + Microsoft\Data\Common\ConnectionString\DbConnectionStringSynonyms.cs + + + Microsoft\Data\Common\ConnectionString\DbConnectionStringUtilities.cs - - Microsoft\Data\Common\DbConnectionPoolKey.cs + + Microsoft\Data\Common\ConnectionString\IpAddressPreferenceUtilities.cs - - Microsoft\Data\Common\DbConnectionStringCommon.cs + + Microsoft\Data\Common\ConnectionString\PoolBlockingUtilities.cs Microsoft\Data\Common\MultipartIdentifier.cs @@ -71,44 +93,65 @@ Microsoft\Data\OperationAbortedException.cs + + Microsoft\Data\ProviderBase\DbConnectionInternal.cs + Microsoft\Data\ProviderBase\DbConnectionClosed.cs Microsoft\Data\ProviderBase\DbConnectionFactory.cs - - Microsoft\Data\ProviderBase\DbConnectionPool.cs + + Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs - - Microsoft\Data\ProviderBase\DbConnectionInternal.cs + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolAuthenticationContext.cs + + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolAuthenticationContextKey.cs + + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolGroup.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolAuthenticationContext.cs + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolGroupProviderInfo.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolAuthenticationContextKey.cs + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolIdentity.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolGroup.cs + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolKey.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolGroupProviderInfo.cs + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolOptions.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolIdentity.cs + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolProviderInfo.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolOptions.cs + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolState.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolProviderInfo.cs + + Microsoft\Data\SqlClient\ConnectionPool\IDbConnectionPool.cs + + + Microsoft\Data\SqlClient\ConnectionPool\SqlConnectionPoolGroupProviderInfo.cs + + + Microsoft\Data\SqlClient\ConnectionPool\SqlConnectionPoolKey.cs + + + Microsoft\Data\SqlClient\ConnectionPool\SqlConnectionPoolProviderInfo.cs + + + Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs Microsoft\Data\ProviderBase\DbMetaDataFactory.cs - Common\Microsoft\Data\ProviderBase\DbReferenceCollection.cs + Microsoft\Data\ProviderBase\DbReferenceCollection.cs Microsoft\Data\ProviderBase\FieldNameLookup.cs @@ -119,8 +162,8 @@ Microsoft\Data\Sql\SqlDataSourceEnumerator.cs - - Microsoft\Data\Sql\SqlDataSourceEnumeratorManagedHelper.cs + + Microsoft\Data\Sql\SqlDataSourceEnumeratorManagedHelper.netcore.cs Microsoft\Data\Sql\SqlDataSourceEnumeratorUtil.cs @@ -145,7 +188,7 @@ Microsoft\Data\SqlClient\AlwaysEncryptedKeyConverter.cs - + Microsoft\Data\SqlClient\ApplicationIntent.cs @@ -161,6 +204,63 @@ Microsoft\Data\SqlClient\DataClassification\SensitivityClassification.cs + + Microsoft\Data\SqlClient\Diagnostics\DiagnosticScope.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\DiagnosticTransactionScope.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientCommandAfter.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientCommandBefore.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientCommandError.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionCloseAfter.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionCloseBefore.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionCloseError.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionOpenAfter.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionOpenBefore.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientConnectionOpenError.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientMetrics.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionCommitAfter.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionCommitBefore.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionCommitError.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionRollbackAfter.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionRollbackBefore.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlClientTransactionRollbackError.netcore.cs + + + Microsoft\Data\SqlClient\Diagnostics\SqlDiagnosticListener.netcore.cs + Microsoft\Data\SqlClient\DisposableTemporaryOnStack.cs @@ -182,6 +282,72 @@ Microsoft\Data\SqlClient\LocalAppContextSwitches.cs + + Microsoft\Data\SqlClient\ManagedSni\ConcurrentQueueSemaphore.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\ResolvedServerSpn.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniAsyncCallback.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniError.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniCommon.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniHandle.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniLoadHandle.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniMarsConnection.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniMarsHandle.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniNetworkStream.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniNpHandle.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniPacket.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniPhysicalHandle.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniProviders.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniProxy.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniSmuxFlags.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniSmuxHeader.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniSslStream.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SniTcpHandle.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SslOverTdsStream.netcore.cs + + + Microsoft\Data\SqlClient\ManagedSni\SslOverTdsStream.NetCoreApp.cs + + + Microsoft\Data\SqlClient\ManagedSni\SsrpClient.netcore.cs + Microsoft\Data\SqlClient\NoneAttestationEnclaveProvider.cs @@ -197,6 +363,15 @@ Microsoft\Data\SqlClient\Reliability\AppConfigManager.cs + + Microsoft\Data\SqlClient\Reliability\IAppContextSwitchOverridesSection.cs + + + Microsoft\Data\SqlClient\Reliability\ISqlConfigurableRetryCommandSection.cs + + + Microsoft\Data\SqlClient\Reliability\ISqlConfigurableRetryConnectionSection.cs + Microsoft\Data\SqlClient\Reliability\SqlRetryingEventArgs.cs @@ -236,15 +411,9 @@ Microsoft\Data\SqlClient\Server\ExtendedClrTypeCode.cs - - Microsoft\Data\SqlClient\Server\ITypedGetters.cs - Microsoft\Data\SqlClient\Server\ITypedGettersV3.cs - - Microsoft\Data\SqlClient\Server\ITypedSetters.cs - Microsoft\Data\SqlClient\Server\ITypedSettersV3.cs @@ -254,12 +423,6 @@ Microsoft\Data\SqlClient\Server\MetadataUtilsSmi.cs - - Microsoft\Data\SqlClient\Server\SmiEventSink.cs - - - Microsoft\Data\SqlClient\Server\SmiEventSink_Default.Common.cs - Microsoft\Data\SqlClient\Server\SmiGettersStream.cs @@ -278,8 +441,8 @@ Microsoft\Data\SqlClient\Server\SmiTypedGetterSetter.cs - - Microsoft\Data\SqlClient\Server\SmiXetterAccessMap.Common.cs + + Microsoft\Data\SqlClient\Server\SmiXetterAccessMap.cs Microsoft\Data\SqlClient\Server\SmiXetterTypeCode.cs @@ -305,6 +468,9 @@ Microsoft\Data\SqlClient\Server\ValueUtilsSmi.cs + + Microsoft\Data\SqlClient\ISqlVector.cs + Microsoft\Data\SqlClient\SignatureVerificationCache.cs @@ -320,6 +486,9 @@ Microsoft\Data\SqlClient\SqlAeadAes256CbcHmac256Factory.cs + + Microsoft\Data\SqlClient\SqlAppContextSwitchManager.netcore.cs + Microsoft\Data\SqlClient\SqlAuthenticationParameters.cs @@ -347,6 +516,9 @@ Microsoft\Data\SqlClient\SqlBuffer.cs + + Microsoft\Data\SqlClient\SqlBulkCopy.cs + Microsoft\Data\SqlClient\SqlBulkCopyColumnMapping.cs @@ -413,14 +585,8 @@ Microsoft\Data\SqlClient\SqlConnectionEncryptOptionConverter.cs - - Microsoft\Data\SqlClient\SqlConnectionPoolGroupProviderInfo.cs - - - Microsoft\Data\SqlClient\SqlConnectionPoolKey.cs - - - Microsoft\Data\SqlClient\SqlConnectionPoolProviderInfo.cs + + Microsoft\Data\SqlClient\SqlConnectionFactory.cs Microsoft\Data\SqlClient\SqlConnectionString.cs @@ -437,6 +603,12 @@ Microsoft\Data\SqlClient\SqlDataAdapter.cs + + Microsoft\Data\SqlClient\SqlDataReader.cs + + + Microsoft\Data\SqlClient\SqlDbColumn.cs + Microsoft\Data\SqlClient\SqlDelegatedTransaction.cs @@ -491,8 +663,8 @@ Microsoft\Data\SqlClient\SqlInternalTransaction.cs - - Microsoft\Data\SqlClient\SqlMetadataFactory.cs + + Microsoft\Data\SqlClient\SqlMetaDataFactory.cs Microsoft\Data\SqlClient\SqlNotificationEventArgs.cs @@ -506,9 +678,6 @@ Microsoft\Data\SqlClient\SqlNotificationType.cs - - Microsoft\Data\SqlClient\SqlObjectPool.cs - Microsoft\Data\SqlClient\SqlParameter.cs @@ -551,20 +720,29 @@ Microsoft\Data\SqlClient\SqlSymmetricKeyCache.cs + + Microsoft\Data\SqlClient\SqlTransaction.cs + Microsoft\Data\SqlClient\SqlUdtInfo.cs Microsoft\Data\SqlClient\SqlUtil.cs - - Microsoft\Data\SqlClient\SqlTransaction.Common.cs + + Microsoft\Data\SqlClient\SSPI\NegotiateSspiContextProvider.cs - - Microsoft\Data\SqlClient\SSPI\NegotiateSSPIContextProvider.cs + + Microsoft\Data\SqlClient\SSPI\SspiContextProvider.cs - - Microsoft\Data\SqlClient\SSPI\SSPIContextProvider.cs + + Microsoft\Data\SqlClient\SSPI\SspiAuthenticationParameters.cs + + + Microsoft\Data\SqlClient\Utilities\ObjectPool.cs + + + Microsoft\Data\SqlClient\Utilities\ObjectPools.cs Microsoft\Data\SqlClient\TdsEnums.cs @@ -599,6 +777,9 @@ Microsoft\Data\SqlClient\VirtualSecureModeEnclaveProviderBase.cs + + Microsoft\Data\SqlDbTypeExtensions.cs + Microsoft\Data\SQLTypes\SQLResource.cs @@ -608,6 +789,9 @@ Microsoft\Data\SqlTypes\SqlJson.cs + + Microsoft\Data\SqlTypes\SqlVector.cs + Resources\ResCategoryAttribute.cs @@ -615,57 +799,20 @@ Resources\ResDescriptionAttribute.cs - Common\System\Diagnostics\CodeAnalysis.cs + System\Diagnostics\CodeAnalysis.cs - - Microsoft\Data\SqlDbTypeExtensions.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - + - @@ -687,34 +834,34 @@ Interop\Windows\Kernel32\Kernel32.cs - Interop\Windows\NtDll\Interop\Windows\NtDll\CreateDisposition.cs + Interop\Windows\NtDll\CreateDisposition.cs - Interop\Windows\NtDll\Interop\Windows\NtDll\CreateOptions.cs + Interop\Windows\NtDll\CreateOptions.cs - Interop\Windows\NtDll\Interop\Windows\NtDll\DesiredAccess.cs + Interop\Windows\NtDll\DesiredAccess.cs - Interop\Windows\NtDll\Interop\Windows\NtDll\FileFullEaInformation.cs + Interop\Windows\NtDll\FileFullEaInformation.cs - Interop\Windows\NtDll\ImpersonaltionLevel.cs + Interop\Windows\NtDll\ImpersonationLevel.cs Interop\Windows\NtDll\IoStatusBlock.cs - Interop\Windows\NtDll\Interop\Windows\NtDll\NtDll.cs + Interop\Windows\NtDll\NtDll.cs - Interop\Windows\NtDll\Interop\Windows\NtDll\ObjectAttributes.cs + Interop\Windows\NtDll\ObjectAttributeFlags.cs - Interop\Windows\NtDll\Interop\Windows\NtDll\ObjectAttributes.cs + Interop\Windows\NtDll\ObjectAttributes.cs - Interop\Windows\NtDll\Interop\Windows\NtDll\SecurityQualityOfService.cs + Interop\Windows\NtDll\SecurityQualityOfService.cs Interop\Windows\Sni\AuthProviderInfo.cs @@ -731,6 +878,9 @@ Interop\Windows\Sni\IoType.cs + + Interop\Windows\Sni\ISniNativeMethods.cs + Interop\Windows\Sni\Prefix.cs @@ -752,6 +902,12 @@ Interop\Windows\Sni\SniError.cs + + Interop\Windows\Sni\SniNativeMethods.netcore.cs + + + Interop\Windows\Sni\SniNativeWrapper.cs + Interop\Windows\Sni\TransparentNetworkResolutionMode.cs @@ -761,23 +917,35 @@ Microsoft\Data\Common\AdapterUtil.Windows.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolIdentity.Windows.cs - Microsoft\Data\Sql\SqlDataSourceEnumeratorNativeHelper.cs Microsoft\Data\Sql\SqlDataSourceEnumerator.Windows.cs + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolIdentity.Windows.cs + + + Microsoft\Data\SqlClient\LocalDb\LocalDbApi.Windows.cs + + + Microsoft\Data\SqlClient\ManagedSni\LocalDB.netcore.Windows.cs + + + Microsoft\Data\SqlClient\PacketHandle.Windows.cs + + + Microsoft\Data\SqlClient\SessionHandle.Windows.cs + Microsoft\Data\SqlClient\SqlColumnEncryptionCngProvider.Windows.cs Microsoft\Data\SqlClient\SqlColumnEncryptionCspProvider.Windows.cs - - Microsoft\Data\SqlClient\SSPI\NativeSSPIContextProvider.cs + + Microsoft\Data\SqlClient\SSPI\NativeSspiContextProvider.cs Microsoft\Data\SqlClient\SqlColumnEncryptionCertificateStoreProvider.Windows.cs @@ -785,44 +953,55 @@ Microsoft\Data\SqlClient\TdsParserSafeHandles.Windows.cs + + Microsoft\Data\SqlClient\TdsParserStateObjectFactory.Windows.cs + Microsoft\Data\SqlTypes\SqlFileStream.Windows.cs - - - - - - - - - Microsoft\Data\Common\AdapterUtil.Unix.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolIdentity.Unix.cs + + Microsoft\Data\Sql\SqlDataSourceEnumerator.netcore.Unix.cs + + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolIdentity.Unix.cs + + + Microsoft\Data\SqlClient\LocalDb\LocalDbApi.Unix.cs + + + Microsoft\Data\SqlClient\ManagedSni\LocalDB.netcore.Unix.cs + + + Microsoft\Data\SqlClient\PacketHandle.netcore.Unix.cs + + + Microsoft\Data\SqlClient\SessionHandle.netcore.Unix.cs + + + Microsoft\Data\SqlClinet\SqlColumnEncryptionCertificateStoreProvider.netcore.Unix.cs + + + Microsoft\Data\SqlClinet\SqlColumnEncryptionCngProvider.netcore.Unix.cs + + + Microsoft\Data\SqlClinet\SqlColumnEncryptionCspProvider.netcore.Unix.cs + + + Microsoft\Data\SqlClient\TdsParserStateObjectFactory.Unix.cs + + + Microsoft\Data\SqlTypes\SqlFileStream.netcore.Unix.cs - - - - - - - - - - - - - @@ -845,25 +1024,28 @@ Resources\%(RecursiveDir)%(Filename)%(Extension) Microsoft.Data.SqlClient.Resources.%(Filename).resources - + Microsoft.Data.SqlClient.SqlMetaData.xml + Resources\Microsoft.Data.SqlClient.SqlMetaData.xml - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionOptions.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionOptions.cs deleted file mode 100644 index 759696d471..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Common/DbConnectionOptions.cs +++ /dev/null @@ -1,74 +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.IO; -using System.Text; - -namespace Microsoft.Data.Common -{ - internal partial class DbConnectionOptions - { - internal string ExpandAttachDbFileName(string replacementValue) - { - int copyPosition = 0; - - StringBuilder builder = new(_usersConnectionString.Length); - for (NameValuePair current = _keyChain; current != null; current = current.Next) - { - if (string.Equals(current.Name, DbConnectionStringKeywords.AttachDBFilename, StringComparison.InvariantCultureIgnoreCase)) - { - builder.Append($"{current.Name}={replacementValue};"); - } - else - { - builder.Append(_usersConnectionString, copyPosition, current.Length); - } - copyPosition += current.Length; - } - - return builder.ToString(); - } - - // SxS notes: - // * this method queries "DataDirectory" value from the current AppDomain. - // This string is used for to replace "!DataDirectory!" values in the connection string, it is not considered as an "exposed resource". - // * This method uses GetFullPath to validate that root path is valid, the result is not exposed out. - internal static string ExpandDataDirectory(string keyword, string value) - { - string fullPath = null; - if (value != null && value.StartsWith(DataDirectory, StringComparison.OrdinalIgnoreCase)) - { - // find the replacement path - object rootFolderObject = AppDomain.CurrentDomain.GetData("DataDirectory"); - var rootFolderPath = (rootFolderObject as string); - if (rootFolderObject != null && rootFolderPath == null) - { - throw ADP.InvalidDataDirectory(); - } - else if (string.IsNullOrEmpty(rootFolderPath)) - { - rootFolderPath = AppDomain.CurrentDomain.BaseDirectory ?? string.Empty; - } - - var fileName = value.Substring(DataDirectory.Length); - - if (Path.IsPathRooted(fileName)) - { - fileName = fileName.TrimStart(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }); - } - - fullPath = Path.Combine(rootFolderPath, fileName); - - // verify root folder path is a real path without unexpected "..\" - if (!Path.GetFullPath(fullPath).StartsWith(rootFolderPath, StringComparison.Ordinal)) - { - throw ADP.InvalidConnectionOptionValue(keyword); - } - } - return fullPath; - } - - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Common.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Common.cs deleted file mode 100644 index 616aad0ec1..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Common.cs +++ /dev/null @@ -1,124 +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.Globalization; -using System.Runtime.InteropServices; -using System.Text; -using Microsoft.Data.SqlClient; - -namespace Microsoft.Data -{ - internal static partial class LocalDBAPI - { - private static LocalDBFormatMessageDelegate s_localDBFormatMessage = null; - - internal static void ReleaseDLLHandles() - { - s_userInstanceDLLHandle = IntPtr.Zero; - s_localDBFormatMessage = null; - } - - - private static LocalDBFormatMessageDelegate LocalDBFormatMessage - { - get - { - if (s_localDBFormatMessage == null) - { - lock (s_dllLock) - { - if (s_localDBFormatMessage == null) - { - IntPtr functionAddr = LoadProcAddress(); - - if (functionAddr == IntPtr.Zero) - { - // SNI checks for LocalDBFormatMessage during DLL loading, so it is practically impossible to get this error. - int hResult = Marshal.GetLastWin32Error(); - SqlClientEventSource.Log.TryTraceEvent("LocalDBAPI.LocalDBFormatMessage> GetProcAddress for LocalDBFormatMessage error 0x{0}", hResult); - throw CreateLocalDBException(errorMessage: Strings.LocalDB_MethodNotFound); - } - s_localDBFormatMessage = Marshal.GetDelegateForFunctionPointer(functionAddr); - } - } - } - return s_localDBFormatMessage; - } - } - - //This is copy of handle that SNI maintains, so we are responsible for freeing it - therefore there we are not using SafeHandle - private static IntPtr s_userInstanceDLLHandle = IntPtr.Zero; - - private static readonly object s_dllLock = new object(); - - - private const uint const_LOCALDB_TRUNCATE_ERR_MESSAGE = 1;// flag for LocalDBFormatMessage that indicates that message can be truncated if it does not fit in the buffer - private const int const_ErrorMessageBufferSize = 1024; // Buffer size for Local DB error message 1K will be enough for all messages - - - internal static string GetLocalDBMessage(int hrCode) - { - Debug.Assert(hrCode < 0, "HRCode does not indicate error"); - try - { - StringBuilder buffer = new StringBuilder((int)const_ErrorMessageBufferSize); - uint len = (uint)buffer.Capacity; - - - // First try for current culture - int hResult = LocalDBFormatMessage(hrLocalDB: hrCode, dwFlags: const_LOCALDB_TRUNCATE_ERR_MESSAGE, dwLanguageId: (uint)CultureInfo.CurrentCulture.LCID, - buffer: buffer, buflen: ref len); - if (hResult >= 0) - return buffer.ToString(); - else - { - // Message is not available for current culture, try default - buffer = new StringBuilder((int)const_ErrorMessageBufferSize); - len = (uint)buffer.Capacity; - hResult = LocalDBFormatMessage(hrLocalDB: hrCode, dwFlags: const_LOCALDB_TRUNCATE_ERR_MESSAGE, dwLanguageId: 0 /* thread locale with fallback to English */, - buffer: buffer, buflen: ref len); - if (hResult >= 0) - return buffer.ToString(); - else - return string.Format(CultureInfo.CurrentCulture, "{0} (0x{1:X}).", Strings.LocalDB_UnobtainableMessage, hResult); - } - } - catch (SqlException exc) - { - return string.Format(CultureInfo.CurrentCulture, "{0} ({1}).", Strings.LocalDB_UnobtainableMessage, exc.Message); - } - } - - - private static SqlException CreateLocalDBException(string errorMessage, uint sniError = 0) - { - Debug.Assert(!string.IsNullOrEmpty(errorMessage), "Error message should not be null or empty"); - - if (sniError != 0) - { - string sniErrorMessage = SQL.GetSNIErrorMessage(sniError); - errorMessage = $"{errorMessage} (error: {sniError} - {sniErrorMessage})"; - } - - SqlErrorCollection collection = new SqlErrorCollection - { - new SqlError( - infoNumber: (int)sniError, - errorState: 0, - errorClass: TdsEnums.FATAL_ERROR_CLASS, - server: null, - errorMessage, - procedure: null, - lineNumber: 0) - }; - - SqlException exc = SqlException.CreateException(collection, serverVersion: null); - exc._doNotReconnect = true; - - return exc; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs deleted file mode 100644 index cd51f9ca82..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Windows.cs +++ /dev/null @@ -1,42 +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 Microsoft.Data.SqlClient; -using Interop.Windows.Sni; - -namespace Microsoft.Data -{ - internal static partial class LocalDBAPI - { - private static IntPtr LoadProcAddress() => SafeNativeMethods.GetProcAddress(UserInstanceDLLHandle, "LocalDBFormatMessage"); - - private static IntPtr UserInstanceDLLHandle - { - get - { - if (s_userInstanceDLLHandle == IntPtr.Zero) - { - lock (s_dllLock) - { - if (s_userInstanceDLLHandle == IntPtr.Zero) - { - SNINativeMethodWrapper.SNIQueryInfo(QueryType.SNI_QUERY_LOCALDB_HMODULE, ref s_userInstanceDLLHandle); - if (s_userInstanceDLLHandle != IntPtr.Zero) - { - SqlClientEventSource.Log.TryTraceEvent("LocalDBAPI.UserInstanceDLLHandle | LocalDB - handle obtained"); - } - else - { - SNINativeMethodWrapper.SNIGetLastError(out SniError sniError); - throw CreateLocalDBException(StringsHelper.GetString("LocalDB_FailedGetDLLHandle"), sniError.sniError); - } - } - } - } - return s_userInstanceDLLHandle; - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.cs deleted file mode 100644 index ba2371c232..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.cs +++ /dev/null @@ -1,46 +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.Runtime.InteropServices; -using System.Text; - -namespace Microsoft.Data -{ - internal static partial class LocalDBAPI - { - private const string LocalDbPrefix = @"(localdb)\"; - private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#"; - - - [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, uint dwFlags, uint dwLanguageId, StringBuilder buffer, ref uint buflen); - - // check if name is in format (localdb)\ and return instance name if it is - // localDB can also have a format of np:\\.\pipe\LOCALDB#\tsql\query - internal static string GetLocalDbInstanceNameFromServerName(string serverName) - { - if (serverName is not null) - { - // it can start with spaces if specified in quotes - // Memory allocation is reduced by using ReadOnlySpan - ReadOnlySpan input = serverName.AsSpan().Trim(); - if (input.StartsWith(LocalDbPrefix.AsSpan(), StringComparison.OrdinalIgnoreCase)) - { - input = input.Slice(LocalDbPrefix.Length); - if (!input.IsEmpty) - { - return input.ToString(); - } - } - else if (input.StartsWith(LocalDbPrefix_NP.AsSpan(), StringComparison.OrdinalIgnoreCase)) - { - return input.ToString(); - } - - } - return null; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/PacketHandle.Unix.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/PacketHandle.Unix.cs deleted file mode 100644 index 63ba0014c4..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/PacketHandle.Unix.cs +++ /dev/null @@ -1,34 +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; - -namespace Microsoft.Data.SqlClient -{ - // this structure is used for transporting packet handle references between the TdsParserStateObject - // base class and Managed or Native implementations. - // It prevents the native IntPtr type from being boxed and prevents the need to cast from object which loses compile time type safety - // It carries type information so that assertions about the type of handle can be made in the implemented abstract methods - // it is a ref struct so that it can only be used to transport the handles and not store them - - // N.B. If you change this type you must also change the version for the other platform - - internal readonly ref struct PacketHandle - { - public const int NativePointerType = 1; - public const int NativePacketType = 2; - public const int ManagedPacketType = 3; - - public readonly SNI.SNIPacket ManagedPacket; - public readonly int Type; - - private PacketHandle(SNI.SNIPacket managedPacket, int type) - { - Type = type; - ManagedPacket = managedPacket; - } - - public static PacketHandle FromManagedPacket(SNI.SNIPacket managedPacket) => new PacketHandle(managedPacket, ManagedPacketType); - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/PacketHandle.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/PacketHandle.Windows.cs deleted file mode 100644 index 15c2ae58eb..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/PacketHandle.Windows.cs +++ /dev/null @@ -1,45 +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; - -namespace Microsoft.Data.SqlClient -{ - // this structure is used for transporting packet handle references between the TdsParserStateObject - // base class and Managed or Native implementations. - // It prevents the native IntPtr type from being boxed and prevents the need to cast from object which loses compile time type safety - // It carries type information so that assertions about the type of handle can be made in the implemented abstract methods - // it is a ref struct so that it can only be used to transport the handles and not store them - - // N.B. If you change this type you must also change the version for the other platform - - internal readonly ref struct PacketHandle - { - public const int NativePointerType = 1; - public const int NativePacketType = 2; - public const int ManagedPacketType = 3; - - public readonly IntPtr NativePointer; - public readonly SNIPacket NativePacket; - - public readonly SNI.SNIPacket ManagedPacket; - public readonly int Type; - - private PacketHandle(IntPtr nativePointer, SNIPacket nativePacket, SNI.SNIPacket managedPacket, int type) - { - Type = type; - ManagedPacket = managedPacket; - NativePointer = nativePointer; - NativePacket = nativePacket; - } - - public static PacketHandle FromManagedPacket(SNI.SNIPacket managedPacket) => new PacketHandle(default, default, managedPacket, ManagedPacketType); - - public static PacketHandle FromNativePointer(IntPtr nativePointer) => new PacketHandle(nativePointer, default, default, NativePointerType); - - public static PacketHandle FromNativePacket(SNIPacket nativePacket) => new PacketHandle(default, nativePacket, default, NativePacketType); - - - } -} \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryLogicManager.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryLogicManager.NetCoreApp.cs deleted file mode 100644 index fc0e4c80a3..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryLogicManager.NetCoreApp.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using System.Runtime.Loader; - -namespace Microsoft.Data.SqlClient -{ - /// - /// Configurable retry logic manager - /// - internal sealed partial class SqlConfigurableRetryLogicLoader - { - #region Type resolution - /// - /// Performs a case-sensitive search to resolve the specified type name - /// and its related assemblies in default assembly load context if they aren't loaded yet. - /// - /// Resolved type if it could resolve the type; otherwise, the `SqlConfigurableRetryFactory` type. - private static Type LoadType(string fullyQualifiedName) - { - string methodName = nameof(LoadType); - SqlClientEventSource.Log.TryTraceEvent(" Entry point.", TypeName, methodName); - - var result = Type.GetType(fullyQualifiedName, AssemblyResolver, TypeResolver); - if (result != null) - { - SqlClientEventSource.Log.TryTraceEvent(" The '{2}' type is resolved.", - TypeName, methodName, result.FullName); - } - else - { - result = typeof(SqlConfigurableRetryFactory); - SqlClientEventSource.Log.TryTraceEvent(" Couldn't resolve the requested type by '{2}'; The internal `{3}` type is returned.", - TypeName, methodName, fullyQualifiedName, result.FullName); - } - SqlClientEventSource.Log.TryTraceEvent(" Exit point.", TypeName, methodName); - return result; - } - - /// - /// If the caller does not have sufficient permissions to read the specified file, - /// no exception is thrown and the method returns null regardless of the existence of path. - /// - private static string MakeFullPath(string directory, string assemblyName, string extension = ".dll") - { - string methodName = nameof(MakeFullPath); - SqlClientEventSource.Log.TryTraceEvent(" Looking for '{2}' assembly in '{3}' directory." - , TypeName, methodName, assemblyName, directory); - string fullPath = Path.Combine(directory, assemblyName); - fullPath = string.IsNullOrEmpty(Path.GetExtension(fullPath)) ? fullPath + extension : fullPath; - SqlClientEventSource.Log.TryTraceEvent(" Looking for '{2}' assembly by '{3}' full path." - , TypeName, methodName, assemblyName, fullPath); - return File.Exists(fullPath) ? fullPath : null; - } - - private static Assembly AssemblyResolver(AssemblyName arg) - { - string methodName = nameof(AssemblyResolver); - - string fullPath = MakeFullPath(Environment.CurrentDirectory, arg.Name); - SqlClientEventSource.Log.TryTraceEvent(" Looking for '{2}' assembly by '{3}' full path." - , TypeName, methodName, arg, fullPath); - - return fullPath == null ? null : AssemblyLoadContext.Default.LoadFromAssemblyPath(fullPath); - } - - private static Type TypeResolver(Assembly arg1, string arg2, bool arg3) - { - IEnumerable types = arg1?.ExportedTypes; - Type result = null; - if (types != null) - { - foreach (Type type in types) - { - if (type.FullName == arg2) - { - if (result != null) - { - throw new InvalidOperationException("Sequence contains more than one matching element"); - } - result = type; - } - } - } - if (result == null) - { - throw new InvalidOperationException("Sequence contains no matching element"); - } - return result; - } - - /// - /// Load assemblies on request. - /// - private static Assembly Default_Resolving(AssemblyLoadContext arg1, AssemblyName arg2) - { - string methodName = nameof(Default_Resolving); - - string target = MakeFullPath(Environment.CurrentDirectory, arg2.Name); - SqlClientEventSource.Log.TryTraceEvent(" Looking for '{2}' assembly that is requested by '{3}' ALC from '{4}' path." - , TypeName, methodName, arg2, arg1, target); - - return target == null ? null : arg1.LoadFromAssemblyPath(target); - } - #endregion - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIError.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIError.cs deleted file mode 100644 index 080e274f94..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIError.cs +++ /dev/null @@ -1,47 +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; - -namespace Microsoft.Data.SqlClient.SNI -{ - /// - /// SNI error - /// - internal class SNIError - { - // Error numbers from native SNI implementation - internal const uint CertificateValidationErrorCode = 2148074277; - - public readonly SNIProviders provider; - public readonly string errorMessage; - public readonly uint nativeError; - public readonly uint sniError; - public readonly string function; - public readonly uint lineNumber; - public readonly Exception exception; - - public SNIError(SNIProviders provider, uint nativeError, uint sniErrorCode, string errorMessage) - { - lineNumber = 0; - function = string.Empty; - this.provider = provider; - this.nativeError = nativeError; - sniError = sniErrorCode; - this.errorMessage = errorMessage; - exception = null; - } - - public SNIError(SNIProviders provider, uint sniErrorCode, Exception sniException, uint nativeErrorCode = 0) - { - lineNumber = 0; - function = string.Empty; - this.provider = provider; - nativeError = nativeErrorCode; - sniError = sniErrorCode; - errorMessage = string.Empty; - exception = sniException; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIStreams.ValueTask.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIStreams.ValueTask.cs deleted file mode 100644 index f5f38f0efe..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIStreams.ValueTask.cs +++ /dev/null @@ -1,109 +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.Threading; -using System.Threading.Tasks; -using System; - -namespace Microsoft.Data.SqlClient.SNI -{ - internal sealed partial class SNISslStream - { - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); - } - - public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) - { - await _readAsyncSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - return await base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) - { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNISslStream), EventType.ERR, "Internal Exception occurred while reading data: {0}", args0: e?.Message); - throw; - } - finally - { - _readAsyncSemaphore.Release(); - } - } - - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return WriteAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); - } - - public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) - { - await _writeAsyncSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - await base.WriteAsync(buffer, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) - { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNISslStream), EventType.ERR, "Internal Exception occurred while reading data: {0}", args0: e?.Message); - throw; - } - finally - { - _writeAsyncSemaphore.Release(); - } - } - } - - internal sealed partial class SNINetworkStream - { - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); - } - - public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) - { - await _readAsyncSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - return await base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) - { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNISslStream), EventType.ERR, "Internal Exception occurred while reading data: {0}", args0: e?.Message); - throw; - } - finally - { - _readAsyncSemaphore.Release(); - } - } - - // Prevent the WriteAsync collisions by running the task in a Semaphore Slim - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return WriteAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); - } - - public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) - { - await _writeAsyncSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - await base.WriteAsync(buffer, cancellationToken).ConfigureAwait(false); - } - catch (Exception e) - { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNISslStream), EventType.ERR, "Internal Exception occurred while reading data: {0}", args0: e?.Message); - throw; - } - finally - { - _writeAsyncSemaphore.Release(); - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIStreams.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIStreams.cs deleted file mode 100644 index 389f25eeae..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIStreams.cs +++ /dev/null @@ -1,41 +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.Net.Security; -using System.IO; -using System.Net.Sockets; - -namespace Microsoft.Data.SqlClient.SNI -{ - /// - /// This class extends SslStream to customize stream behavior for Managed SNI implementation. - /// - internal sealed partial class SNISslStream : SslStream - { - private readonly ConcurrentQueueSemaphore _writeAsyncSemaphore; - private readonly ConcurrentQueueSemaphore _readAsyncSemaphore; - - public SNISslStream(Stream innerStream, bool leaveInnerStreamOpen, RemoteCertificateValidationCallback userCertificateValidationCallback) - : base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback) - { - _writeAsyncSemaphore = new ConcurrentQueueSemaphore(1); - _readAsyncSemaphore = new ConcurrentQueueSemaphore(1); - } - } - - /// - /// This class extends NetworkStream to customize stream behavior for Managed SNI implementation. - /// - internal sealed partial class SNINetworkStream : NetworkStream - { - private readonly ConcurrentQueueSemaphore _writeAsyncSemaphore; - private readonly ConcurrentQueueSemaphore _readAsyncSemaphore; - - public SNINetworkStream(Socket socket, bool ownsSocket) : base(socket, ownsSocket) - { - _writeAsyncSemaphore = new ConcurrentQueueSemaphore(1); - _readAsyncSemaphore = new ConcurrentQueueSemaphore(1); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientDiagnostic.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientDiagnostic.cs deleted file mode 100644 index 51a2f8f0ba..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientDiagnostic.cs +++ /dev/null @@ -1,970 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Data; - -namespace Microsoft.Data.SqlClient.Diagnostics -{ - /// - public sealed class SqlClientCommandBefore : IReadOnlyList> - { - /// - public const string Name = "Microsoft.Data.SqlClient.WriteCommandBefore"; - - internal SqlClientCommandBefore(Guid operationId, string operation, long timestamp, Guid? connectionId, long? transactionId, SqlCommand command) - { - OperationId = operationId; - Operation = operation; - Timestamp = timestamp; - ConnectionId = connectionId; - TransactionId = transactionId; - Command = command; - } - - /// - public Guid OperationId { get; } - /// - public string Operation { get; } - /// - public long Timestamp { get; } - /// - public Guid? ConnectionId { get; } - /// - public long? TransactionId { get; } - /// - public SqlCommand Command { get; } - - /// - public int Count => 3 + 3; - - /// - public KeyValuePair this[int index] - { - get => index switch - { - 0 => new KeyValuePair(nameof(OperationId), OperationId), - 1 => new KeyValuePair(nameof(Operation), Operation), - 2 => new KeyValuePair(nameof(Timestamp), Timestamp), - 3 => new KeyValuePair(nameof(ConnectionId), ConnectionId), - 4 => new KeyValuePair(nameof(TransactionId), TransactionId), - 5 => new KeyValuePair(nameof(Command), Command), - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - public IEnumerator> GetEnumerator() - { - int count = Count; - for (int index = 0; index < count; index++) - { - yield return this[index]; - } - } - } - - /// - public sealed class SqlClientCommandAfter : IReadOnlyList> - { - /// - public const string Name = "Microsoft.Data.SqlClient.WriteCommandAfter"; - - internal SqlClientCommandAfter(Guid operationId, string operation, long timestamp, Guid? connectionId, long? transactionId, SqlCommand command, IDictionary statistics) - { - OperationId = operationId; - Operation = operation; - Timestamp = timestamp; - ConnectionId = connectionId; - TransactionId = transactionId; - Command = command; - Statistics = statistics; - } - - /// - public Guid OperationId { get; } - /// - public string Operation { get; } - /// - public long Timestamp { get; } - /// - public Guid? ConnectionId { get; } - /// - public long? TransactionId { get; } - /// - public SqlCommand Command { get; } - /// - public IDictionary Statistics { get; } - - /// - public int Count => 3 + 4; - - /// - public KeyValuePair this[int index] - { - get => index switch - { - 0 => new KeyValuePair(nameof(OperationId), OperationId), - 1 => new KeyValuePair(nameof(Operation), Operation), - 2 => new KeyValuePair(nameof(Timestamp), Timestamp), - 3 => new KeyValuePair(nameof(ConnectionId), ConnectionId), - 4 => new KeyValuePair(nameof(TransactionId), TransactionId), - 5 => new KeyValuePair(nameof(Command), Command), - 6 => new KeyValuePair(nameof(Statistics), Statistics), - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - public IEnumerator> GetEnumerator() - { - int count = Count; - for (int index = 0; index < count; index++) - { - yield return this[index]; - } - } - } - - /// - public sealed class SqlClientCommandError : IReadOnlyList> - { - /// - - public const string Name = "Microsoft.Data.SqlClient.WriteCommandError"; - - internal SqlClientCommandError(Guid operationId, string operation, long timestamp, Guid? connectionId, long? transactionId, SqlCommand command, Exception exception) - { - OperationId = operationId; - Operation = operation; - Timestamp = timestamp; - ConnectionId = connectionId; - TransactionId = transactionId; - Command = command; - Exception = exception; - } - /// - public Guid OperationId { get; } - /// - public string Operation { get; } - /// - public long Timestamp { get; } - /// - public Guid? ConnectionId { get; } - /// - public long? TransactionId { get; } - /// - public SqlCommand Command { get; } - /// - public Exception Exception { get; } - - /// - public int Count => 3 + 4; - - /// - public KeyValuePair this[int index] - { - get => index switch - { - 0 => new KeyValuePair(nameof(OperationId), OperationId), - 1 => new KeyValuePair(nameof(Operation), Operation), - 2 => new KeyValuePair(nameof(Timestamp), Timestamp), - 3 => new KeyValuePair(nameof(ConnectionId), ConnectionId), - 4 => new KeyValuePair(nameof(TransactionId), TransactionId), - 5 => new KeyValuePair(nameof(Command), Command), - 6 => new KeyValuePair(nameof(Exception), Exception), - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - public IEnumerator> GetEnumerator() - { - int count = Count; - for (int index = 0; index < count; index++) - { - yield return this[index]; - } - } - } - - /// - public sealed class SqlClientConnectionOpenBefore : IReadOnlyList> - { - /// - public const string Name = "Microsoft.Data.SqlClient.WriteConnectionOpenBefore"; - - internal SqlClientConnectionOpenBefore(Guid operationId, string operation, long timestamp, SqlConnection connection, string clientVersion) - { - OperationId = operationId; - Operation = operation; - Timestamp = timestamp; - Connection = connection; - ClientVersion = clientVersion; - } - - /// - public Guid OperationId { get; } - /// - public string Operation { get; } - /// - public long Timestamp { get; } - /// - public SqlConnection Connection { get; } - /// - public string ClientVersion { get; } - - /// - public int Count => 3 + 2; - - /// - public KeyValuePair this[int index] - { - get => index switch - { - 0 => new KeyValuePair(nameof(OperationId), OperationId), - 1 => new KeyValuePair(nameof(Operation), Operation), - 2 => new KeyValuePair(nameof(Timestamp), Timestamp), - 3 => new KeyValuePair(nameof(Connection), Connection), - 4 => new KeyValuePair(nameof(ClientVersion), ClientVersion), - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - public IEnumerator> GetEnumerator() - { - int count = Count; - for (int index = 0; index < count; index++) - { - yield return this[index]; - } - } - } - - /// - public sealed class SqlClientConnectionOpenAfter : IReadOnlyList> - { - /// - public const string Name = "Microsoft.Data.SqlClient.WriteConnectionOpenAfter"; - - internal SqlClientConnectionOpenAfter(Guid operationId, string operation, long timestamp, Guid connectionId, SqlConnection connection, string clientVersion, IDictionary statistics) - { - OperationId = operationId; - Operation = operation; - Timestamp = timestamp; - ConnectionId = connectionId; - Connection = connection; - ClientVersion = clientVersion; - Statistics = statistics; - } - - /// - public Guid OperationId { get; } - /// - public string Operation { get; } - /// - public long Timestamp { get; } - /// - public Guid ConnectionId { get; } - /// - public SqlConnection Connection { get; } - /// - public string ClientVersion { get; } - /// - public IDictionary Statistics { get; } - - /// - public int Count => 3 + 4; - - /// - public KeyValuePair this[int index] - { - get => index switch - { - 0 => new KeyValuePair(nameof(OperationId), OperationId), - 1 => new KeyValuePair(nameof(Operation), Operation), - 2 => new KeyValuePair(nameof(Timestamp), Timestamp), - 3 => new KeyValuePair(nameof(ConnectionId), ConnectionId), - 4 => new KeyValuePair(nameof(Connection), Connection), - 5 => new KeyValuePair(nameof(ClientVersion), ClientVersion), - 6 => new KeyValuePair(nameof(Statistics), Statistics), - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - public IEnumerator> GetEnumerator() - { - int count = Count; - for (int index = 0; index < count; index++) - { - yield return this[index]; - } - } - } - - /// - public sealed class SqlClientConnectionOpenError : IReadOnlyList> - { - /// - - public const string Name = "Microsoft.Data.SqlClient.WriteConnectionOpenError"; - - internal SqlClientConnectionOpenError(Guid operationId, string operation, long timestamp, Guid connectionId, SqlConnection connection, string clientVersion, Exception exception) - { - OperationId = operationId; - Operation = operation; - Timestamp = timestamp; - ConnectionId = connectionId; - Connection = connection; - ClientVersion = clientVersion; - Exception = exception; - } - - /// - public Guid OperationId { get; } - /// - public string Operation { get; } - /// - public long Timestamp { get; } - - /// - public Guid ConnectionId { get; } - /// - public SqlConnection Connection { get; } - /// - public string ClientVersion { get; } - /// - public Exception Exception { get; } - - /// - public int Count => 3 + 4; - - /// - public KeyValuePair this[int index] - { - get => index switch - { - 0 => new KeyValuePair(nameof(OperationId), OperationId), - 1 => new KeyValuePair(nameof(Operation), Operation), - 2 => new KeyValuePair(nameof(Timestamp), Timestamp), - 3 => new KeyValuePair(nameof(ConnectionId), ConnectionId), - 4 => new KeyValuePair(nameof(Connection), Connection), - 5 => new KeyValuePair(nameof(ClientVersion), ClientVersion), - 6 => new KeyValuePair(nameof(Exception), Exception), - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - public IEnumerator> GetEnumerator() - { - int count = Count; - for (int index = 0; index < count; index++) - { - yield return this[index]; - } - } - } - - /// - public sealed class SqlClientConnectionCloseBefore : IReadOnlyList> - { - /// - public const string Name = "Microsoft.Data.SqlClient.WriteConnectionCloseBefore"; - - internal SqlClientConnectionCloseBefore(Guid operationId, string operation, long timestamp, Guid? connectionId, SqlConnection connection, IDictionary statistics) - { - OperationId = operationId; - Operation = operation; - Timestamp = timestamp; - ConnectionId = connectionId; - Connection = connection; - Statistics = statistics; - } - - /// - public Guid OperationId { get; } - /// - public string Operation { get; } - /// - public long Timestamp { get; } - /// - public Guid? ConnectionId { get; } - /// - public SqlConnection Connection { get; } - /// - public IDictionary Statistics { get; } - - /// - public int Count => 3 + 3; - - /// - public KeyValuePair this[int index] - { - get => index switch - { - 0 => new KeyValuePair(nameof(OperationId), OperationId), - 1 => new KeyValuePair(nameof(Operation), Operation), - 2 => new KeyValuePair(nameof(Timestamp), Timestamp), - 3 => new KeyValuePair(nameof(ConnectionId), ConnectionId), - 4 => new KeyValuePair(nameof(Connection), Connection), - 5 => new KeyValuePair(nameof(Statistics), Statistics), - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - public IEnumerator> GetEnumerator() - { - int count = Count; - for (int index = 0; index < count; index++) - { - yield return this[index]; - } - } - } - - /// - public sealed class SqlClientConnectionCloseAfter : IReadOnlyList> - { - /// - public const string Name = "Microsoft.Data.SqlClient.WriteConnectionCloseAfter"; - - internal SqlClientConnectionCloseAfter(Guid operationId, string operation, long timestamp, Guid? connectionId, SqlConnection connection, IDictionary statistics) - { - OperationId = operationId; - Operation = operation; - Timestamp = timestamp; - ConnectionId = connectionId; - Connection = connection; - Statistics = statistics; - } - - /// - public Guid OperationId { get; } - /// - public string Operation { get; } - /// - public long Timestamp { get; } - /// - public Guid? ConnectionId { get; } - /// - public SqlConnection Connection { get; } - /// - public IDictionary Statistics { get; } - - /// - public int Count => 3 + 3; - - /// - public KeyValuePair this[int index] - { - get => index switch - { - 0 => new KeyValuePair(nameof(OperationId), OperationId), - 1 => new KeyValuePair(nameof(Operation), Operation), - 2 => new KeyValuePair(nameof(Timestamp), Timestamp), - 3 => new KeyValuePair(nameof(ConnectionId), ConnectionId), - 4 => new KeyValuePair(nameof(Connection), Connection), - 5 => new KeyValuePair(nameof(Statistics), Statistics), - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - public IEnumerator> GetEnumerator() - { - int count = Count; - for (int index = 0; index < count; index++) - { - yield return this[index]; - } - } - } - - /// - public sealed class SqlClientConnectionCloseError : IReadOnlyList> - { - /// - public const string Name = "Microsoft.Data.SqlClient.WriteConnectionCloseError"; - - internal SqlClientConnectionCloseError(Guid operationId, string operation, long timestamp, Guid? connectionId, SqlConnection connection, IDictionary statistics, Exception ex) - { - OperationId = operationId; - Operation = operation; - Timestamp = timestamp; - ConnectionId = connectionId; - Connection = connection; - Statistics = statistics; - Exception = ex; - } - - /// - public Guid OperationId { get; } - /// - public string Operation { get; } - /// - public long Timestamp { get; } - - /// - public Guid? ConnectionId { get; } - /// - public SqlConnection Connection { get; } - /// - public IDictionary Statistics { get; } - - /// - public Exception Exception { get; } - - /// - public int Count => 3 + 4; - - /// - public KeyValuePair this[int index] - { - get => index switch - { - 0 => new KeyValuePair(nameof(OperationId), OperationId), - 1 => new KeyValuePair(nameof(Operation), Operation), - 2 => new KeyValuePair(nameof(Timestamp), Timestamp), - 3 => new KeyValuePair(nameof(ConnectionId), ConnectionId), - 4 => new KeyValuePair(nameof(Connection), Connection), - 5 => new KeyValuePair(nameof(Statistics), Statistics), - 6 => new KeyValuePair(nameof(Exception), Exception), - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - public IEnumerator> GetEnumerator() - { - int count = Count; - for (int index = 0; index < count; index++) - { - yield return this[index]; - } - } - } - - /// - public sealed class SqlClientTransactionCommitBefore : IReadOnlyList> - { - /// - public const string Name = "Microsoft.Data.SqlClient.WriteTransactionCommitBefore"; - - internal SqlClientTransactionCommitBefore(Guid operationId, string operation, long timestamp, IsolationLevel isolationLevel, SqlConnection connection, long? transactionId) - { - OperationId = operationId; - Operation = operation; - Timestamp = timestamp; - IsolationLevel = isolationLevel; - Connection = connection; - TransactionId = transactionId; - } - - /// - public Guid OperationId { get; } - /// - public string Operation { get; } - /// - public long Timestamp { get; } - /// - public IsolationLevel IsolationLevel { get; } - /// - public SqlConnection Connection { get; } - /// - public long? TransactionId { get; } - - /// - public int Count => 3 + 3; - - /// - public KeyValuePair this[int index] - { - get => index switch - { - 0 => new KeyValuePair(nameof(OperationId), OperationId), - 1 => new KeyValuePair(nameof(Operation), Operation), - 2 => new KeyValuePair(nameof(Timestamp), Timestamp), - 3 => new KeyValuePair(nameof(IsolationLevel), IsolationLevel), - 4 => new KeyValuePair(nameof(Connection), Connection), - 5 => new KeyValuePair(nameof(TransactionId), TransactionId), - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - public IEnumerator> GetEnumerator() - { - int count = Count; - for (int index = 0; index < count; index++) - { - yield return this[index]; - } - } - } - - /// - public sealed class SqlClientTransactionCommitAfter : IReadOnlyList> - { - /// - public const string Name = "Microsoft.Data.SqlClient.WriteTransactionCommitAfter"; - - internal SqlClientTransactionCommitAfter(Guid operationId, string operation, long timestamp, IsolationLevel isolationLevel, SqlConnection connection, long? transactionId) - { - OperationId = operationId; - Operation = operation; - Timestamp = timestamp; - IsolationLevel = isolationLevel; - Connection = connection; - TransactionId = transactionId; - } - - /// - public Guid OperationId { get; } - /// - public string Operation { get; } - /// - public long Timestamp { get; } - - /// - public IsolationLevel IsolationLevel { get; } - /// - public SqlConnection Connection { get; } - /// - public long? TransactionId { get; } - - /// - public int Count => 3 + 3; - - /// - public KeyValuePair this[int index] - { - get => index switch - { - 0 => new KeyValuePair(nameof(OperationId), OperationId), - 1 => new KeyValuePair(nameof(Operation), Operation), - 2 => new KeyValuePair(nameof(Timestamp), Timestamp), - 3 => new KeyValuePair(nameof(IsolationLevel), IsolationLevel), - 4 => new KeyValuePair(nameof(Connection), Connection), - 5 => new KeyValuePair(nameof(TransactionId), TransactionId), - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - public IEnumerator> GetEnumerator() - { - int count = Count; - for (int index = 0; index < count; index++) - { - yield return this[index]; - } - } - } - - /// - public sealed class SqlClientTransactionCommitError : IReadOnlyList> - { - /// - public const string Name = "Microsoft.Data.SqlClient.WriteTransactionCommitError"; - - internal SqlClientTransactionCommitError(Guid operationId, string operation, long timestamp, IsolationLevel isolationLevel, SqlConnection connection, long? transactionId, Exception ex) - { - OperationId = operationId; - Operation = operation; - Timestamp = timestamp; - IsolationLevel = isolationLevel; - Connection = connection; - TransactionId = transactionId; - Exception = ex; - } - - /// - public Guid OperationId { get; } - /// - public string Operation { get; } - /// - public long Timestamp { get; } - - /// - public IsolationLevel IsolationLevel { get; } - /// - public SqlConnection Connection { get; } - /// - public long? TransactionId { get; } - /// - public Exception Exception { get; } - - /// - public int Count => 3 + 4; - - /// - public KeyValuePair this[int index] - { - get => index switch - { - 0 => new KeyValuePair(nameof(OperationId), OperationId), - 1 => new KeyValuePair(nameof(Operation), Operation), - 2 => new KeyValuePair(nameof(Timestamp), Timestamp), - 3 => new KeyValuePair(nameof(IsolationLevel), IsolationLevel), - 4 => new KeyValuePair(nameof(Connection), Connection), - 5 => new KeyValuePair(nameof(TransactionId), TransactionId), - 6 => new KeyValuePair(nameof(Exception), Exception), - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - public IEnumerator> GetEnumerator() - { - int count = Count; - for (int index = 0; index < count; index++) - { - yield return this[index]; - } - } - } - - /// - public sealed class SqlClientTransactionRollbackBefore : IReadOnlyList> - { - /// - public const string Name = "Microsoft.Data.SqlClient.WriteTransactionRollbackBefore"; - - internal SqlClientTransactionRollbackBefore(Guid operationId, string operation, long timestamp, IsolationLevel isolationLevel, SqlConnection connection, long? transactionId, string transactionName) - { - OperationId = operationId; - Operation = operation; - Timestamp = timestamp; - IsolationLevel = isolationLevel; - Connection = connection; - TransactionId = transactionId; - TransactionName = transactionName; - } - - /// - public Guid OperationId { get; } - /// - public string Operation { get; } - /// - public long Timestamp { get; } - - /// - public IsolationLevel IsolationLevel { get; } - /// - public SqlConnection Connection { get; } - /// - public long? TransactionId { get; } - /// - public string TransactionName { get; } - - /// - public int Count => 3 + 4; - - /// - public KeyValuePair this[int index] - { - get => index switch - { - 0 => new KeyValuePair(nameof(OperationId), OperationId), - 1 => new KeyValuePair(nameof(Operation), Operation), - 2 => new KeyValuePair(nameof(Timestamp), Timestamp), - 3 => new KeyValuePair(nameof(IsolationLevel), IsolationLevel), - 4 => new KeyValuePair(nameof(Connection), Connection), - 5 => new KeyValuePair(nameof(TransactionId), TransactionId), - 6 => new KeyValuePair(nameof(TransactionName), TransactionName), - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - public IEnumerator> GetEnumerator() - { - int count = Count; - for (int index = 0; index < count; index++) - { - yield return this[index]; - } - } - } - - /// - public sealed class SqlClientTransactionRollbackAfter : IReadOnlyList> - { - /// - public const string Name = "Microsoft.Data.SqlClient.WriteTransactionRollbackAfter"; - - internal SqlClientTransactionRollbackAfter(Guid operationId, string operation, long timestamp, IsolationLevel isolationLevel, SqlConnection connection, long? transactionId, string transactionName) - { - OperationId = operationId; - Operation = operation; - Timestamp = timestamp; - IsolationLevel = isolationLevel; - Connection = connection; - TransactionId = transactionId; - TransactionName = transactionName; - } - - /// - public Guid OperationId { get; } - /// - public string Operation { get; } - /// - public long Timestamp { get; } - /// - public IsolationLevel IsolationLevel { get; } - /// - public SqlConnection Connection { get; } - /// - public long? TransactionId { get; } - /// - public string TransactionName { get; } - - /// - public int Count => 3 + 4; - - /// - public KeyValuePair this[int index] - { - get => index switch - { - 0 => new KeyValuePair(nameof(OperationId), OperationId), - 1 => new KeyValuePair(nameof(Operation), Operation), - 2 => new KeyValuePair(nameof(Timestamp), Timestamp), - 3 => new KeyValuePair(nameof(IsolationLevel), IsolationLevel), - 4 => new KeyValuePair(nameof(Connection), Connection), - 5 => new KeyValuePair(nameof(TransactionId), TransactionId), - 6 => new KeyValuePair(nameof(TransactionName), TransactionName), - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - public IEnumerator> GetEnumerator() - { - int count = Count; - for (int index = 0; index < count; index++) - { - yield return this[index]; - } - } - } - - /// - public sealed class SqlClientTransactionRollbackError : IReadOnlyList> - { - /// - public const string Name = "Microsoft.Data.SqlClient.WriteTransactionRollbackError"; - - internal SqlClientTransactionRollbackError(Guid operationId, string operation, long timestamp, IsolationLevel isolationLevel, SqlConnection connection, long? transactionId, string transactionName, Exception ex) - { - OperationId = operationId; - Operation = operation; - Timestamp = timestamp; - IsolationLevel = isolationLevel; - Connection = connection; - TransactionId = transactionId; - TransactionName = transactionName; - Exception = ex; - } - - - /// - public Guid OperationId { get; } - /// - public string Operation { get; } - /// - public long Timestamp { get; } - /// - public IsolationLevel IsolationLevel { get; } - /// - public SqlConnection Connection { get; } - /// - public long? TransactionId { get; } - /// - public string TransactionName { get; } - /// - public Exception Exception { get; } - - /// - public int Count => 3 + 5; - - /// - public KeyValuePair this[int index] - { - get => index switch - { - 0 => new KeyValuePair(nameof(OperationId), OperationId), - 1 => new KeyValuePair(nameof(Operation), Operation), - 2 => new KeyValuePair(nameof(Timestamp), Timestamp), - 3 => new KeyValuePair(nameof(IsolationLevel), IsolationLevel), - 4 => new KeyValuePair(nameof(Connection), Connection), - 5 => new KeyValuePair(nameof(TransactionId), TransactionId), - 6 => new KeyValuePair(nameof(TransactionName), TransactionName), - 7 => new KeyValuePair(nameof(Exception), Exception), - _ => throw new IndexOutOfRangeException(nameof(index)), - }; - } - - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - /// - public IEnumerator> GetEnumerator() - { - int count = Count; - for (int index = 0; index < count; index++) - { - yield return this[index]; - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientDiagnosticListenerExtensions.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientDiagnosticListenerExtensions.cs deleted file mode 100644 index 9f63cbaba8..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientDiagnosticListenerExtensions.cs +++ /dev/null @@ -1,507 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Data; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using Microsoft.Data.SqlClient.Diagnostics; - -namespace Microsoft.Data.SqlClient -{ - /// - /// Extension methods on the DiagnosticListener class to log SqlCommand data - /// - internal static class SqlClientDiagnosticListenerExtensions - { - public const string DiagnosticListenerName = "SqlClientDiagnosticListener"; - - public static Guid WriteCommandBefore(this SqlDiagnosticListener @this, SqlCommand sqlCommand, SqlTransaction transaction, [CallerMemberName] string operation = "") - { - if (@this.IsEnabled(SqlClientCommandBefore.Name)) - { - Guid operationId = Guid.NewGuid(); - - @this.Write( - SqlClientCommandBefore.Name, - new SqlClientCommandBefore( - operationId, - operation, - Stopwatch.GetTimestamp(), - sqlCommand.Connection?.ClientConnectionId, - transaction?.InternalTransaction?.TransactionId, - sqlCommand - ) - ); - - return operationId; - } - else - { - return Guid.Empty; - } - } - - public static void WriteCommandAfter(this SqlDiagnosticListener @this, Guid operationId, SqlCommand sqlCommand, SqlTransaction transaction, [CallerMemberName] string operation = "") - { - if (@this.IsEnabled(SqlClientCommandAfter.Name)) - { - @this.Write( - SqlClientCommandAfter.Name, - new SqlClientCommandAfter( - operationId, - operation, - Stopwatch.GetTimestamp(), - sqlCommand.Connection?.ClientConnectionId, - transaction?.InternalTransaction?.TransactionId, - sqlCommand, - sqlCommand.Statistics?.GetDictionary() - ) - ); - } - } - - public static void WriteCommandError(this SqlDiagnosticListener @this, Guid operationId, SqlCommand sqlCommand, SqlTransaction transaction, Exception ex, [CallerMemberName] string operation = "") - { - if (@this.IsEnabled(SqlClientCommandError.Name)) - { - @this.Write( - SqlClientCommandError.Name, - new SqlClientCommandError - ( - operationId, - operation, - Stopwatch.GetTimestamp(), - sqlCommand.Connection?.ClientConnectionId, - transaction?.InternalTransaction?.TransactionId, - sqlCommand, - ex - ) - ); - } - } - - public static Guid WriteConnectionOpenBefore(this SqlDiagnosticListener @this, SqlConnection sqlConnection, [CallerMemberName] string operation = "") - { - if (@this.IsEnabled(SqlClientConnectionOpenBefore.Name)) - { - Guid operationId = Guid.NewGuid(); - - @this.Write( - SqlClientConnectionOpenBefore.Name, - new SqlClientConnectionOpenBefore - ( - operationId, - operation, - Stopwatch.GetTimestamp(), - sqlConnection, - ThisAssembly.InformationalVersion - ) - ); - - return operationId; - } - else - { - return Guid.Empty; - } - } - - public static void WriteConnectionOpenAfter(this SqlDiagnosticListener @this, Guid operationId, SqlConnection sqlConnection, [CallerMemberName] string operation = "") - { - if (@this.IsEnabled(SqlClientConnectionOpenAfter.Name)) - { - @this.Write( - SqlClientConnectionOpenAfter.Name, - new SqlClientConnectionOpenAfter - ( - operationId, - operation, - Stopwatch.GetTimestamp(), - sqlConnection.ClientConnectionId, - sqlConnection, - ThisAssembly.InformationalVersion, - sqlConnection.Statistics?.GetDictionary() - ) - ); - } - } - - public static void WriteConnectionOpenError(this SqlDiagnosticListener @this, Guid operationId, SqlConnection sqlConnection, Exception ex, [CallerMemberName] string operation = "") - { - if (@this.IsEnabled(SqlClientConnectionOpenError.Name)) - { - @this.Write( - SqlClientConnectionOpenError.Name, - new SqlClientConnectionOpenError - ( - operationId, - operation, - Stopwatch.GetTimestamp(), - sqlConnection.ClientConnectionId, - sqlConnection, - ThisAssembly.InformationalVersion, - ex - ) - ); - } - } - - public static Guid WriteConnectionCloseBefore(this SqlDiagnosticListener @this, SqlConnection sqlConnection, [CallerMemberName] string operation = "") - { - if (@this.IsEnabled(SqlClientConnectionCloseBefore.Name)) - { - Guid operationId = Guid.NewGuid(); - - @this.Write( - SqlClientConnectionCloseBefore.Name, - new SqlClientConnectionCloseBefore - ( - operationId, - operation, - Stopwatch.GetTimestamp(), - sqlConnection.ClientConnectionId, - sqlConnection, - sqlConnection.Statistics?.GetDictionary() - ) - ); - - return operationId; - } - else - return Guid.Empty; - } - - public static void WriteConnectionCloseAfter(this SqlDiagnosticListener @this, Guid operationId, Guid clientConnectionId, SqlConnection sqlConnection, [CallerMemberName] string operation = "") - { - if (@this.IsEnabled(SqlClientConnectionCloseAfter.Name)) - { - @this.Write( - SqlClientConnectionCloseAfter.Name, - new SqlClientConnectionCloseAfter - ( - operationId, - operation, - Stopwatch.GetTimestamp(), - clientConnectionId, - sqlConnection, - sqlConnection.Statistics?.GetDictionary() - ) - ); - } - } - - public static void WriteConnectionCloseError(this SqlDiagnosticListener @this, Guid operationId, Guid clientConnectionId, SqlConnection sqlConnection, Exception ex, [CallerMemberName] string operation = "") - { - if (@this.IsEnabled(SqlClientConnectionCloseError.Name)) - { - @this.Write( - SqlClientConnectionCloseError.Name, - new SqlClientConnectionCloseError - ( - operationId, - operation, - Stopwatch.GetTimestamp(), - clientConnectionId, - sqlConnection, - sqlConnection.Statistics?.GetDictionary(), - ex - ) - ); - } - } - - public static Guid WriteTransactionCommitBefore(this SqlDiagnosticListener @this, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, [CallerMemberName] string operation = "") - { - if (@this.IsEnabled(SqlClientTransactionCommitBefore.Name)) - { - Guid operationId = Guid.NewGuid(); - - @this.Write( - SqlClientTransactionCommitBefore.Name, - new SqlClientTransactionCommitBefore - ( - operationId, - operation, - Stopwatch.GetTimestamp(), - isolationLevel, - connection, - transaction?.TransactionId - ) - ); - - return operationId; - } - else - { - return Guid.Empty; - } - } - - public static void WriteTransactionCommitAfter(this SqlDiagnosticListener @this, Guid operationId, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, [CallerMemberName] string operation = "") - { - if (@this.IsEnabled(SqlClientTransactionCommitAfter.Name)) - { - @this.Write( - SqlClientTransactionCommitAfter.Name, - new SqlClientTransactionCommitAfter - ( - operationId, - operation, - Stopwatch.GetTimestamp(), - isolationLevel, - connection, - transaction?.TransactionId - ) - ); - } - } - - public static void WriteTransactionCommitError(this SqlDiagnosticListener @this, Guid operationId, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, Exception ex, [CallerMemberName] string operation = "") - { - if (@this.IsEnabled(SqlClientTransactionCommitError.Name)) - { - @this.Write( - SqlClientTransactionCommitError.Name, - new SqlClientTransactionCommitError - ( - operationId, - operation, - Stopwatch.GetTimestamp(), - isolationLevel, - connection, - transaction?.TransactionId, - ex - ) - ); - } - } - - public static Guid WriteTransactionRollbackBefore(this SqlDiagnosticListener @this, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, string transactionName = null, [CallerMemberName] string operation = "") - { - if (@this.IsEnabled(SqlClientTransactionRollbackBefore.Name)) - { - Guid operationId = Guid.NewGuid(); - - @this.Write( - SqlClientTransactionRollbackBefore.Name, - new SqlClientTransactionRollbackBefore - ( - operationId, - operation, - Stopwatch.GetTimestamp(), - isolationLevel, - connection, - transaction?.TransactionId, - transactionName - ) - ); - - return operationId; - } - else - { - return Guid.Empty; - } - } - - public static void WriteTransactionRollbackAfter(this SqlDiagnosticListener @this, Guid operationId, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, string transactionName = null, [CallerMemberName] string operation = "") - { - if (@this.IsEnabled(SqlClientTransactionRollbackAfter.Name)) - { - @this.Write( - SqlClientTransactionRollbackAfter.Name, - new SqlClientTransactionRollbackAfter - ( - operationId, - operation, - Stopwatch.GetTimestamp(), - isolationLevel, - connection, - transaction?.TransactionId, - transactionName - ) - ); - } - } - - public static void WriteTransactionRollbackError(this SqlDiagnosticListener @this, Guid operationId, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, Exception ex, string transactionName = null, [CallerMemberName] string operation = "") - { - if (@this.IsEnabled(SqlClientTransactionRollbackError.Name)) - { - @this.Write( - SqlClientTransactionRollbackError.Name, - new SqlClientTransactionRollbackError - ( - operationId, - operation, - Stopwatch.GetTimestamp(), - isolationLevel, - connection, - transaction?.TransactionId, - transactionName, - ex - ) - ); - } - } - - public static DiagnosticScope CreateCommandScope(this SqlDiagnosticListener @this, SqlCommand command, SqlTransaction transaction, [CallerMemberName] string operationName = "") - { - return DiagnosticScope.CreateCommandScope(@this, command, transaction, operationName); - } - - public static DiagnosticTransactionScope CreateTransactionCommitScope(this SqlDiagnosticListener @this, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, [CallerMemberName] string operationName = "") - { - return DiagnosticTransactionScope.CreateTransactionCommitScope(@this, isolationLevel, connection, transaction, operationName); - } - - public static DiagnosticTransactionScope CreateTransactionRollbackScope(this SqlDiagnosticListener @this, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, string transactionName, [CallerMemberName] string operationName = "") - { - return DiagnosticTransactionScope.CreateTransactionRollbackScope(@this, isolationLevel, connection, transaction, transactionName, operationName); - } - } - - internal ref struct DiagnosticScope //: IDisposable //ref structs cannot implement interfaces but the compiler will use pattern matching - { - private const int CommandOperation = 1; - private const int ConnectionOpenOperation = 2; - - private readonly SqlDiagnosticListener _diagnostics; - private readonly int _operation; - private readonly string _operationName; - private readonly Guid _operationId; - private readonly object _context1; - private readonly object _context2; - private Exception _exception; - - private DiagnosticScope(SqlDiagnosticListener diagnostics, int operation, Guid operationsId, string operationName, object context1, object context2) - { - _diagnostics = diagnostics; - _operation = operation; - _operationId = operationsId; - _operationName = operationName; - _context1 = context1; - _context2 = context2; - _exception = null; - } - - public void Dispose() - { - switch (_operation) - { - case CommandOperation: - if (_exception != null) - { - _diagnostics.WriteCommandError(_operationId, (SqlCommand)_context1, (SqlTransaction)_context2, _exception, _operationName); - } - else - { - _diagnostics.WriteCommandAfter(_operationId, (SqlCommand)_context1, (SqlTransaction)_context2, _operationName); - } - break; - - case ConnectionOpenOperation: - if (_exception != null) - { - _diagnostics.WriteConnectionOpenError(_operationId, (SqlConnection)_context1, _exception, _operationName); - } - else - { - _diagnostics.WriteConnectionOpenAfter(_operationId, (SqlConnection)_context1, _operationName); - } - break; - - // ConnectionCloseOperation is not implemented because it is conditionally emitted and that requires manual calls to the write apis - } - } - - public void SetException(Exception ex) - { - _exception = ex; - } - - public static DiagnosticScope CreateCommandScope(SqlDiagnosticListener diagnostics, SqlCommand command, SqlTransaction transaction, [CallerMemberName] string operationName = "") - { - Guid operationId = diagnostics.WriteCommandBefore(command, transaction, operationName); - return new DiagnosticScope(diagnostics, CommandOperation, operationId, operationName, command, transaction); - } - } - - internal ref struct DiagnosticTransactionScope //: IDisposable //ref structs cannot implement interfaces but the compiler will use pattern matching - { - public const int TransactionCommit = 1; - public const int TransactionRollback = 2; - - private readonly SqlDiagnosticListener _diagnostics; - private readonly int _operation; - private readonly Guid _operationId; - private readonly string _operationName; - private readonly IsolationLevel _isolationLevel; - private readonly SqlConnection _connection; - private readonly SqlInternalTransaction _transaction; - private readonly string _transactionName; - private Exception _exception; - - public DiagnosticTransactionScope(SqlDiagnosticListener diagnostics, int operation, Guid operationId, string operationName, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, string transactionName) - { - _diagnostics = diagnostics; - _operation = operation; - _operationId = operationId; - _operationName = operationName; - _isolationLevel = isolationLevel; - _connection = connection; - _transaction = transaction; - _transactionName = transactionName; - _exception = null; - } - - public void Dispose() - { - switch (_operation) - { - case TransactionCommit: - if (_exception != null) - { - _diagnostics.WriteTransactionCommitError(_operationId, _isolationLevel, _connection, _transaction, _exception, _operationName); - } - else - { - _diagnostics.WriteTransactionCommitAfter(_operationId, _isolationLevel, _connection, _transaction, _operationName); - } - break; - - case TransactionRollback: - if (_exception != null) - { - _diagnostics.WriteTransactionRollbackError(_operationId, _isolationLevel, _connection, _transaction, _exception, _transactionName, _operationName); - } - else - { - _diagnostics.WriteTransactionRollbackAfter(_operationId, _isolationLevel, _connection, _transaction, _transactionName, _operationName); - } - break; - } - } - - public void SetException(Exception ex) - { - _exception = ex; - } - - public static DiagnosticTransactionScope CreateTransactionCommitScope(SqlDiagnosticListener diagnostics, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, [CallerMemberName] string operationName = "") - { - Guid operationId = diagnostics.WriteTransactionCommitBefore(isolationLevel, connection, transaction, operationName); - return new DiagnosticTransactionScope(diagnostics, TransactionCommit, operationId, operationName, isolationLevel, connection, transaction, null); - } - - public static DiagnosticTransactionScope CreateTransactionRollbackScope(SqlDiagnosticListener diagnostics, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, string transactionName, [CallerMemberName] string operationName = "") - { - Guid operationId = diagnostics.WriteTransactionRollbackBefore(isolationLevel, connection, transaction, transactionName, operationName); - return new DiagnosticTransactionScope(diagnostics, TransactionCommit, operationId, operationName, isolationLevel, connection, transaction, transactionName); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientEventSource.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientEventSource.NetCoreApp.cs deleted file mode 100644 index c0312ca219..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlClientEventSource.NetCoreApp.cs +++ /dev/null @@ -1,396 +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.Tracing; -using System.Threading; - -namespace Microsoft.Data.SqlClient -{ - /// - /// supported frameworks: .Net core 3.1 and .Net standard 2.1 and above - /// - internal partial class SqlClientEventSource : SqlClientEventSourceBase - { - private PollingCounter _activeHardConnections; - private IncrementingPollingCounter _hardConnectsPerSecond; - private IncrementingPollingCounter _hardDisconnectsPerSecond; - - private PollingCounter _activeSoftConnections; - private IncrementingPollingCounter _softConnects; - private IncrementingPollingCounter _softDisconnects; - - private PollingCounter _numberOfNonPooledConnections; - private PollingCounter _numberOfPooledConnections; - - private PollingCounter _numberOfActiveConnectionPoolGroups; - private PollingCounter _numberOfInactiveConnectionPoolGroups; - - private PollingCounter _numberOfActiveConnectionPools; - private PollingCounter _numberOfInactiveConnectionPools; - - private PollingCounter _numberOfActiveConnections; - private PollingCounter _numberOfFreeConnections; - private PollingCounter _numberOfStasisConnections; - private IncrementingPollingCounter _numberOfReclaimedConnections; - - private long _activeHardConnectionsCounter = 0; - private long _hardConnectsCounter = 0; - private long _hardDisconnectsCounter = 0; - - private long _activeSoftConnectionsCounter = 0; - private long _softConnectsCounter = 0; - private long _softDisconnectsCounter = 0; - - private long _nonPooledConnectionsCounter = 0; - private long _pooledConnectionsCounter = 0; - - private long _activeConnectionPoolGroupsCounter = 0; - private long _inactiveConnectionPoolGroupsCounter = 0; - - private long _activeConnectionPoolsCounter = 0; - private long _inactiveConnectionPoolsCounter = 0; - - private long _activeConnectionsCounter = 0; - private long _freeConnectionsCounter = 0; - private long _stasisConnectionsCounter = 0; - private long _reclaimedConnectionsCounter = 0; - - protected override void EventCommandMethodCall(EventCommandEventArgs command) - { - if(command.Command != EventCommand.Enable) - { - return; - } - - _activeHardConnections = _activeHardConnections ?? - new PollingCounter("active-hard-connections", this, () => _activeHardConnectionsCounter) - { - DisplayName = "Actual active connections currently made to servers", - DisplayUnits = "count" - }; - - _hardConnectsPerSecond = _hardConnectsPerSecond ?? - new IncrementingPollingCounter("hard-connects", this, () => _hardConnectsCounter) - { - DisplayName = "Actual connection rate to servers", - DisplayUnits = "count / sec", - DisplayRateTimeScale = TimeSpan.FromSeconds(1) - }; - - _hardDisconnectsPerSecond = _hardDisconnectsPerSecond ?? - new IncrementingPollingCounter("hard-disconnects", this, () => _hardDisconnectsCounter) - { - DisplayName = "Actual disconnection rate from servers", - DisplayUnits = "count / sec", - DisplayRateTimeScale = TimeSpan.FromSeconds(1) - }; - - _activeSoftConnections = _activeSoftConnections ?? - new PollingCounter("active-soft-connects", this, () => _activeSoftConnectionsCounter) - { - DisplayName = "Active connections retrieved from the connection pool", - DisplayUnits = "count" - }; - - _softConnects = _softConnects ?? - new IncrementingPollingCounter("soft-connects", this, () => _softConnectsCounter) - { - DisplayName = "Rate of connections retrieved from the connection pool", - DisplayUnits = "count / sec", - DisplayRateTimeScale = TimeSpan.FromSeconds(1) - }; - - _softDisconnects = _softDisconnects ?? - new IncrementingPollingCounter("soft-disconnects", this, () => _softDisconnectsCounter) - { - DisplayName = "Rate of connections returned to the connection pool", - DisplayUnits = "count / sec", - DisplayRateTimeScale = TimeSpan.FromSeconds(1) - }; - - _numberOfNonPooledConnections = _numberOfNonPooledConnections ?? - new PollingCounter("number-of-non-pooled-connections", this, () => _nonPooledConnectionsCounter) - { - DisplayName = "Number of connections not using connection pooling", - DisplayUnits = "count" - }; - - _numberOfPooledConnections = _numberOfPooledConnections ?? - new PollingCounter("number-of-pooled-connections", this, () => _pooledConnectionsCounter) - { - DisplayName = "Number of connections managed by the connection pool", - DisplayUnits = "count" - }; - - _numberOfActiveConnectionPoolGroups = _numberOfActiveConnectionPoolGroups ?? - new PollingCounter("number-of-active-connection-pool-groups", this, () => _activeConnectionPoolGroupsCounter) - { - DisplayName = "Number of active unique connection strings", - DisplayUnits = "count" - }; - - _numberOfInactiveConnectionPoolGroups = _numberOfInactiveConnectionPoolGroups ?? - new PollingCounter("number-of-inactive-connection-pool-groups", this, () => _inactiveConnectionPoolGroupsCounter) - { - DisplayName = "Number of unique connection strings waiting for pruning", - DisplayUnits = "count" - }; - - _numberOfActiveConnectionPools = _numberOfActiveConnectionPools ?? - new PollingCounter("number-of-active-connection-pools", this, () => _activeConnectionPoolsCounter) - { - DisplayName = "Number of active connection pools", - DisplayUnits = "count" - }; - - _numberOfInactiveConnectionPools = _numberOfInactiveConnectionPools ?? - new PollingCounter("number-of-inactive-connection-pools", this, () => _inactiveConnectionPoolsCounter) - { - DisplayName = "Number of inactive connection pools", - DisplayUnits = "count" - }; - - _numberOfActiveConnections = _numberOfActiveConnections ?? - new PollingCounter("number-of-active-connections", this, () => _activeConnectionsCounter) - { - DisplayName = "Number of active connections", - DisplayUnits = "count" - }; - - _numberOfFreeConnections = _numberOfFreeConnections ?? - new PollingCounter("number-of-free-connections", this, () => _freeConnectionsCounter) - { - DisplayName = "Number of ready connections in the connection pool", - DisplayUnits = "count" - }; - - _numberOfStasisConnections = _numberOfStasisConnections ?? - new PollingCounter("number-of-stasis-connections", this, () => _stasisConnectionsCounter) - { - DisplayName = "Number of connections currently waiting to be ready", - DisplayUnits = "count" - }; - - _numberOfReclaimedConnections = _numberOfReclaimedConnections ?? - new IncrementingPollingCounter("number-of-reclaimed-connections", this, () => _reclaimedConnectionsCounter) - { - DisplayName = "Number of reclaimed connections from GC", - DisplayUnits = "count", - DisplayRateTimeScale = TimeSpan.FromSeconds(1) - }; - } - - /// - /// The number of actual connections that are being made to servers - /// - [NonEvent] - internal override void HardConnectRequest() - { - Interlocked.Increment(ref _activeHardConnectionsCounter); - Interlocked.Increment(ref _hardConnectsCounter); - } - - /// - /// The number of actual disconnects that are being made to servers - /// - [NonEvent] - internal override void HardDisconnectRequest() - { - Interlocked.Decrement(ref _activeHardConnectionsCounter); - Interlocked.Increment(ref _hardDisconnectsCounter); - } - - /// - /// The number of connections we get from the pool - /// - [NonEvent] - internal override void SoftConnectRequest() - { - Interlocked.Increment(ref _activeSoftConnectionsCounter); - Interlocked.Increment(ref _softConnectsCounter); - } - - /// - /// The number of connections we return to the pool - /// - [NonEvent] - internal override void SoftDisconnectRequest() - { - Interlocked.Decrement(ref _activeSoftConnectionsCounter); - Interlocked.Increment(ref _softDisconnectsCounter); - } - - /// - /// The number of connections that are not using connection pooling - /// - [NonEvent] - internal override void EnterNonPooledConnection() - { - Interlocked.Increment(ref _nonPooledConnectionsCounter); - } - - /// - /// The number of connections that are not using connection pooling - /// - [NonEvent] - internal override void ExitNonPooledConnection() - { - Interlocked.Decrement(ref _nonPooledConnectionsCounter); - } - - /// - /// The number of connections that are managed by the connection pool - /// - [NonEvent] - internal override void EnterPooledConnection() - { - Interlocked.Increment(ref _pooledConnectionsCounter); - } - - /// - /// The number of connections that are managed by the connection pool - /// - [NonEvent] - internal override void ExitPooledConnection() - { - Interlocked.Decrement(ref _pooledConnectionsCounter); - } - - /// - /// The number of unique connection strings - /// - [NonEvent] - internal override void EnterActiveConnectionPoolGroup() - { - Interlocked.Increment(ref _activeConnectionPoolGroupsCounter); - } - - /// - /// The number of unique connection strings - /// - [NonEvent] - internal override void ExitActiveConnectionPoolGroup() - { - Interlocked.Decrement(ref _activeConnectionPoolGroupsCounter); - } - - /// - /// The number of unique connection strings waiting for pruning - /// - [NonEvent] - internal override void EnterInactiveConnectionPoolGroup() - { - Interlocked.Increment(ref _inactiveConnectionPoolGroupsCounter); - } - - /// - /// The number of unique connection strings waiting for pruning - /// - [NonEvent] - internal override void ExitInactiveConnectionPoolGroup() - { - Interlocked.Decrement(ref _inactiveConnectionPoolGroupsCounter); - } - - /// - /// The number of connection pools - /// - [NonEvent] - internal override void EnterActiveConnectionPool() - { - Interlocked.Increment(ref _activeConnectionPoolsCounter); - } - - /// - /// The number of connection pools - /// - [NonEvent] - internal override void ExitActiveConnectionPool() - { - Interlocked.Decrement(ref _activeConnectionPoolsCounter); - } - - /// - /// The number of connection pools - /// - [NonEvent] - internal override void EnterInactiveConnectionPool() - { - Interlocked.Increment(ref _inactiveConnectionPoolsCounter); - } - - /// - /// The number of connection pools - /// - [NonEvent] - internal override void ExitInactiveConnectionPool() - { - Interlocked.Decrement(ref _inactiveConnectionPoolsCounter); - } - - /// - /// The number of connections currently in-use - /// - [NonEvent] - internal override void EnterActiveConnection() - { - Interlocked.Increment(ref _activeConnectionsCounter); - } - - /// - /// The number of connections currently in-use - /// - [NonEvent] - internal override void ExitActiveConnection() - { - Interlocked.Decrement(ref _activeConnectionsCounter); - } - - /// - /// The number of connections currently available for use - /// - [NonEvent] - internal override void EnterFreeConnection() - { - Interlocked.Increment(ref _freeConnectionsCounter); - } - - /// - /// The number of connections currently available for use - /// - [NonEvent] - internal override void ExitFreeConnection() - { - Interlocked.Decrement(ref _freeConnectionsCounter); - } - - /// - /// The number of connections currently waiting to be made ready for use - /// - [NonEvent] - internal override void EnterStasisConnection() - { - Interlocked.Increment(ref _stasisConnectionsCounter); - } - - /// - /// The number of connections currently waiting to be made ready for use - /// - [NonEvent] - internal override void ExitStasisConnection() - { - Interlocked.Decrement(ref _stasisConnectionsCounter); - } - - /// - /// The number of connections we reclaim from GC'd external connections - /// - [NonEvent] - internal override void ReclaimedConnectionRequest() - { - Interlocked.Increment(ref _reclaimedConnectionsCounter); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs index e64d81da40..47349b492e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -35,7 +35,7 @@ public sealed partial class SqlCommand : DbCommand, ICloneable { private static int _objectTypeCount; // EventSource Counter private const int MaxRPCNameLength = 1046; - internal readonly int ObjectID = Interlocked.Increment(ref _objectTypeCount); private string _commandText; + internal readonly int ObjectID = Interlocked.Increment(ref _objectTypeCount); internal sealed class ExecuteReaderAsyncCallContext : AAsyncCallContext { @@ -113,6 +113,7 @@ protected override void AfterCleared(SqlCommand owner) } } + private string _commandText; private CommandType _commandType; private int? _commandTimeout; private UpdateRowSource _updatedRowSource = UpdateRowSource.Both; @@ -159,7 +160,7 @@ protected override void AfterCleared(SqlCommand owner) private static bool _forceRetryableEnclaveQueryExecutionExceptionDuringGenerateEnclavePackage = false; #endif - private static readonly SqlDiagnosticListener s_diagnosticListener = new SqlDiagnosticListener(SqlClientDiagnosticListenerExtensions.DiagnosticListenerName); + private static readonly SqlDiagnosticListener s_diagnosticListener = new SqlDiagnosticListener(); private bool _parentOperationStarted = false; internal static readonly Action s_cancelIgnoreFailure = CancelIgnoreFailureCallback; @@ -2445,9 +2446,16 @@ long firstAttemptStart _cachedAsyncState.ResetAsyncState(); } - _activeConnection.GetOpenTdsConnection().DecrementAsyncCount(); + try + { + _activeConnection.GetOpenTdsConnection().DecrementAsyncCount(); - globalCompletion.TrySetException(e); + globalCompletion.TrySetException(e); + } + catch (Exception e2) + { + globalCompletion.TrySetException(e2); + } } else { @@ -2464,21 +2472,26 @@ long firstAttemptStart TdsParserStaticMethods.GetRemainingTimeout(timeout, firstAttemptStart), true /*inRetry*/, asyncWrite); - retryTask.ContinueWith(retryTsk => - { - if (retryTsk.IsFaulted) - { - globalCompletion.TrySetException(retryTsk.Exception.InnerException); - } - else if (retryTsk.IsCanceled) - { - globalCompletion.TrySetCanceled(); - } - else + retryTask.ContinueWith( + static (Task retryTask, object state) => { - globalCompletion.TrySetResult(retryTsk.Result); - } - }, TaskScheduler.Default); + TaskCompletionSource completion = (TaskCompletionSource)state; + if (retryTask.IsFaulted) + { + completion.TrySetException(retryTask.Exception.InnerException); + } + else if (retryTask.IsCanceled) + { + completion.TrySetCanceled(); + } + else + { + completion.TrySetResult(retryTask.Result); + } + }, + state: globalCompletion, + TaskScheduler.Default + ); } catch (Exception e2) { @@ -2782,7 +2795,7 @@ private Task InternalExecuteReaderAsync(CommandBehavior behavior, } source.SetException(e); - context.Dispose(); + context?.Dispose(); } return returnedTask; @@ -3039,7 +3052,6 @@ private Task InternalExecuteXmlReaderAsync(CancellationToken cancella } context.Set(this, source, registration, operationId); - Task returnedTask = source.Task; try { @@ -6423,6 +6435,16 @@ internal string BuildParamList(TdsParser parser, SqlParameterCollection paramete paramList.Append(scale); paramList.Append(')'); } + else if (mt.SqlDbType == SqlDbTypeExtensions.Vector) + { + // The validate function for SqlParameters would + // have already thrown InvalidCastException if an incompatible + // value is specified for SqlDbType Vector. + var sqlVectorProps = (ISqlVector)sqlParam.Value; + paramList.Append('('); + paramList.Append(sqlVectorProps.Length); + paramList.Append(')'); + } else if (!mt.IsFixed && !mt.IsLong && mt.SqlDbType != SqlDbType.Timestamp && mt.SqlDbType != SqlDbType.Udt && SqlDbType.Structured != mt.SqlDbType) { int size = sqlParam.Size; diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs index 863996629e..72dec5a28e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -18,7 +18,9 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; using Microsoft.Data.SqlClient.Diagnostics; using Microsoft.SqlServer.Server; @@ -66,7 +68,7 @@ private enum CultureCheckState : uint private SqlRetryLogicBaseProvider _retryLogicProvider; // diagnostics listener - private static readonly SqlDiagnosticListener s_diagnosticListener = new SqlDiagnosticListener(SqlClientDiagnosticListenerExtensions.DiagnosticListenerName); + private static readonly SqlDiagnosticListener s_diagnosticListener = new SqlDiagnosticListener(); // Transient Fault handling flag. This is needed to convey to the downstream mechanism of connection establishment, if Transient Fault handling should be used or not // The downstream handling of Connection open is the same for idle connection resiliency. Currently we want to apply transient fault handling only to the connections opened @@ -98,7 +100,7 @@ private static readonly Dictionary /// /// Global custom provider list should be provided by the user. We shallow copy the user supplied dictionary into a ReadOnlyDictionary. - /// Global custom provider list can only supplied once per application. + /// Global custom provider list can only be supplied once per application. /// private static IReadOnlyDictionary s_globalCustomColumnEncryptionKeyStoreProviders; @@ -163,7 +165,7 @@ public SqlConnection(string connectionString) : this() /// public SqlConnection(string connectionString, SqlCredential credential) : this() { - ConnectionString = connectionString; + ConnectionString = connectionString; // setting connection string first so that ConnectionOption is available if (credential != null) { // The following checks are necessary as setting Credential property will call CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential @@ -193,19 +195,19 @@ public SqlConnection(string connectionString, SqlCredential credential) : this() } else if (UsesActiveDirectoryManagedIdentity(connectionOptions)) { - throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringBuilderUtil.ActiveDirectoryManagedIdentityString); + throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringUtilities.ActiveDirectoryManagedIdentityString); } else if (UsesActiveDirectoryMSI(connectionOptions)) { - throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringBuilderUtil.ActiveDirectoryMSIString); + throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringUtilities.ActiveDirectoryMSIString); } else if (UsesActiveDirectoryDefault(connectionOptions)) { - throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringBuilderUtil.ActiveDirectoryDefaultString); + throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringUtilities.ActiveDirectoryDefaultString); } else if (UsesActiveDirectoryWorkloadIdentity(connectionOptions)) { - throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringBuilderUtil.ActiveDirectoryWorkloadIdentityString); + throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringUtilities.ActiveDirectoryWorkloadIdentityString); } Credential = credential; @@ -234,7 +236,7 @@ private SqlConnection(SqlConnection connection) internal static bool TryGetSystemColumnEncryptionKeyStoreProvider(string keyStoreName, out SqlColumnEncryptionKeyStoreProvider provider) { - return s_systemColumnEncryptionKeyStoreProviders.TryGetValue(keyStoreName, out provider); + return s_systemColumnEncryptionKeyStoreProviders.TryGetValue(keyStoreName, out provider); } /// @@ -274,9 +276,9 @@ internal static List GetColumnEncryptionSystemKeyStoreProvidersNames() { if (s_systemColumnEncryptionKeyStoreProviders.Count > 0) { - return new List(s_systemColumnEncryptionKeyStoreProviders.Keys); + return [.. s_systemColumnEncryptionKeyStoreProviders.Keys]; } - return new List(0); + return []; } /// @@ -289,13 +291,13 @@ internal List GetColumnEncryptionCustomKeyStoreProvidersNames() if (_customColumnEncryptionKeyStoreProviders is not null && _customColumnEncryptionKeyStoreProviders.Count > 0) { - return new List(_customColumnEncryptionKeyStoreProviders.Keys); + return [.. _customColumnEncryptionKeyStoreProviders.Keys]; } if (s_globalCustomColumnEncryptionKeyStoreProviders is not null) { - return new List(s_globalCustomColumnEncryptionKeyStoreProviders.Keys); + return [.. s_globalCustomColumnEncryptionKeyStoreProviders.Keys]; } - return new List(0); + return []; } /// @@ -323,7 +325,9 @@ public static void RegisterColumnEncryptionKeyStoreProviders(IDictionary /// Get enclave attestation url to be used with enclave based Always Encrypted /// - internal string EnclaveAttestationUrl => ((SqlConnectionString)ConnectionOptions).EnclaveAttestationUrl; + internal string EnclaveAttestationUrl + { + get => ((SqlConnectionString)ConnectionOptions).EnclaveAttestationUrl; + } /// /// Get attestation protocol /// internal SqlConnectionAttestationProtocol AttestationProtocol { - get - { - SqlConnectionString opt = (SqlConnectionString)ConnectionOptions; - return opt.AttestationProtocol; - } + get => ((SqlConnectionString)ConnectionOptions).AttestationProtocol; } /// @@ -620,19 +622,19 @@ public override string ConnectionString } else if (UsesActiveDirectoryManagedIdentity(connectionOptions)) { - throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringBuilderUtil.ActiveDirectoryManagedIdentityString); + throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringUtilities.ActiveDirectoryManagedIdentityString); } else if (UsesActiveDirectoryMSI(connectionOptions)) { - throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringBuilderUtil.ActiveDirectoryMSIString); + throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringUtilities.ActiveDirectoryMSIString); } else if (UsesActiveDirectoryDefault(connectionOptions)) { - throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringBuilderUtil.ActiveDirectoryDefaultString); + throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringUtilities.ActiveDirectoryDefaultString); } else if (UsesActiveDirectoryWorkloadIdentity(connectionOptions)) { - throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringBuilderUtil.ActiveDirectoryWorkloadIdentityString); + throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringUtilities.ActiveDirectoryWorkloadIdentityString); } CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); @@ -657,6 +659,7 @@ public override string ConnectionString /// [ResDescription(StringsHelper.ResourceNames.SqlConnection_ConnectionTimeout)] [ResCategory(StringsHelper.ResourceNames.SqlConnection_DataSource)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public override int ConnectionTimeout { get @@ -687,7 +690,6 @@ public string AccessToken { get { - string result = _accessToken; // When a connection is connecting or is ever opened, make AccessToken available only if "Persist Security Info" is set to true // otherwise, return null SqlConnectionString connectionOptions = (SqlConnectionString)UserConnectionOptions; @@ -739,6 +741,7 @@ public Func [ResDescription(StringsHelper.ResourceNames.SqlConnection_Database)] [ResCategory(StringsHelper.ResourceNames.SqlConnection_DataSource)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public override string Database { // if the connection is open, we need to ask the inner connection what it's @@ -765,6 +768,7 @@ public override string Database /// /// To indicate the IsSupported flag sent by the server for DNS Caching. This property is for internal testing only. /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] internal string SQLDNSCachingSupportedState { get @@ -788,6 +792,7 @@ internal string SQLDNSCachingSupportedState /// /// To indicate the IsSupported flag sent by the server for DNS Caching before redirection. This property is for internal testing only. /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] internal string SQLDNSCachingSupportedStateBeforeRedirect { get @@ -1004,19 +1009,19 @@ public SqlCredential Credential } else if (UsesActiveDirectoryManagedIdentity(connectionOptions)) { - throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringBuilderUtil.ActiveDirectoryManagedIdentityString); + throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringUtilities.ActiveDirectoryManagedIdentityString); } else if (UsesActiveDirectoryMSI(connectionOptions)) { - throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringBuilderUtil.ActiveDirectoryMSIString); + throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringUtilities.ActiveDirectoryMSIString); } else if (UsesActiveDirectoryDefault(connectionOptions)) { - throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringBuilderUtil.ActiveDirectoryDefaultString); + throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringUtilities.ActiveDirectoryDefaultString); } else if (UsesActiveDirectoryWorkloadIdentity(connectionOptions)) { - throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringBuilderUtil.ActiveDirectoryWorkloadIdentityString); + throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringUtilities.ActiveDirectoryWorkloadIdentityString); } CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); @@ -1078,7 +1083,7 @@ private void CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken(S throw ADP.InvalidMixedUsageOfCredentialAndAccessToken(); } - if(_accessTokenCallback != null) + if (_accessTokenCallback != null) { throw ADP.InvalidMixedUsageOfAccessTokenAndTokenCallback(); } @@ -1100,7 +1105,7 @@ private void CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessTokenCa throw ADP.InvalidMixedUsageOfAccessTokenCallbackAndAuthentication(); } - if(_accessToken != null) + if (_accessToken != null) { throw ADP.InvalidMixedUsageOfAccessTokenAndTokenCallback(); } @@ -1112,8 +1117,6 @@ protected override DbProviderFactory DbProviderFactory get => SqlClientFactory.Instance; } - // SqlCredential: Pair User Id and password in SecureString which are to be used for SQL authentication - // // PUBLIC EVENTS // @@ -1154,11 +1157,11 @@ protected override void OnStateChange(StateChangeEventArgs stateChange) new public SqlTransaction BeginTransaction() { // this is just a delegate. The actual method tracks executiontime - return BeginTransaction(System.Data.IsolationLevel.Unspecified, null); + return BeginTransaction(IsolationLevel.Unspecified, null); } /// - new public SqlTransaction BeginTransaction(System.Data.IsolationLevel iso) + new public SqlTransaction BeginTransaction(IsolationLevel iso) { // this is just a delegate. The actual method tracks executiontime return BeginTransaction(iso, null); @@ -1171,18 +1174,18 @@ public SqlTransaction BeginTransaction(string transactionName) // BEGIN...COMMIT or BEGIN...ROLLBACK statements. Transaction names // are ignored for nested BEGIN's. The only way to rollback a nested // transaction is to have a save point from a SAVE TRANSACTION call. - return BeginTransaction(System.Data.IsolationLevel.Unspecified, transactionName); + return BeginTransaction(IsolationLevel.Unspecified, transactionName); } /// [SuppressMessage("Microsoft.Reliability", "CA2004:RemoveCallsToGCKeepAlive")] - override protected DbTransaction BeginDbTransaction(System.Data.IsolationLevel isolationLevel) + override protected DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) { using (TryEventScope.Create("SqlConnection.BeginDbTransaction | API | Object Id {0}, Isolation Level {1}", ObjectID, (int)isolationLevel)) { DbTransaction transaction = BeginTransaction(isolationLevel); - // InnerConnection doesn't maintain a ref on the outer connection (this) and + // VSTFDEVDIV# 560355 - InnerConnection doesn't maintain a ref on the outer connection (this) and // subsequently leaves open the possibility that the outer connection could be GC'ed before the SqlTransaction // is fully hooked up (leaving a DbTransaction with a null connection property). Ensure that this is reachable // until the completion of BeginTransaction with KeepAlive @@ -1193,7 +1196,7 @@ override protected DbTransaction BeginDbTransaction(System.Data.IsolationLevel i } /// - public SqlTransaction BeginTransaction(System.Data.IsolationLevel iso, string transactionName) + public SqlTransaction BeginTransaction(IsolationLevel iso, string transactionName) { WaitForPendingReconnection(); SqlStatistics statistics = null; @@ -1233,11 +1236,35 @@ public override void ChangeDatabase(string database) SqlStatistics statistics = null; RepairInnerConnection(); SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlConnection.ChangeDatabase | API | Correlation | Object Id {0}, Activity Id {1}, Database {2}", ObjectID, ActivityCorrelator.Current, database); + TdsParser bestEffortCleanupTarget = null; + +#if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); +#endif try { + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(this); statistics = SqlStatistics.StartTimer(Statistics); InnerConnection.ChangeDatabase(database); } + catch (System.OutOfMemoryException e) + { + Abort(e); + throw; + } + catch (System.StackOverflowException e) + { + Abort(e); + throw; + } + catch (System.Threading.ThreadAbortException e) + { + Abort(e); +#if NETFRAMEWORK + SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); +#endif + throw; + } finally { SqlStatistics.StopTimer(statistics); @@ -1262,7 +1289,6 @@ public static void ClearPool(SqlConnection connection) } } - private void CloseInnerConnection() { // CloseConnection() now handles the lock @@ -1301,10 +1327,15 @@ public override void Close() } SqlStatistics statistics = null; - + TdsParser bestEffortCleanupTarget = null; Exception e = null; + +#if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); +#endif try { + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(this); statistics = SqlStatistics.StartTimer(Statistics); Task reconnectTask = _currentReconnectionTask; @@ -1330,6 +1361,27 @@ public override void Close() _statistics._closeTimestamp = ADP.TimerCurrent(); } } + catch (System.OutOfMemoryException ex) + { + e = ex; + Abort(ex); + throw; + } + catch (System.StackOverflowException ex) + { + e = ex; + Abort(ex); + throw; + } + catch (System.Threading.ThreadAbortException ex) + { + e = ex; + Abort(ex); +#if NETFRAMEWORK + SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); +#endif + throw; + } catch (Exception ex) { e = ex; @@ -1574,6 +1626,7 @@ internal Task ValidateAndReconnect(Action beforeDisconnect, int timeout) _originalConnectionId = ClientConnectionId; SqlClientEventSource.Log.TryTraceEvent("SqlConnection.ValidateAndReconnect | Info | Connection Client Connection Id {0} is invalid, reconnecting", _originalConnectionId); _recoverySessionData = cData; + if (beforeDisconnect != null) { beforeDisconnect(); @@ -1826,6 +1879,7 @@ internal void Retry(Task retryTask) { SqlClientEventSource.Log.TryTraceEvent("SqlConnection.Retry | Info | Object Id {0}", _parent?.ObjectID); _registration.Dispose(); + try { SqlStatistics statistics = null; @@ -2027,7 +2081,6 @@ internal bool HasLocalTransactionFromAPI } } - internal bool Is2008OrNewer { get @@ -2049,7 +2102,6 @@ internal TdsParser Parser } } - // // INTERNAL METHODS // @@ -2101,7 +2153,6 @@ internal void OnError(SqlException exception, bool breakConnection, Action - public sealed partial class SqlFileStream : System.IO.Stream - { - /// - public SqlFileStream(string path, byte[] transactionContext, FileAccess access) - { - throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); - } - - /// - public SqlFileStream(string path, byte[] transactionContext, FileAccess access, FileOptions options, Int64 allocationSize) - { - throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); - } - - /// - public string Name { get { throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); } } - /// - public byte[] TransactionContext { get { throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); } } - /// - public override bool CanRead { get { throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); } } - /// - public override bool CanSeek { get { throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); } } - /// - public override bool CanWrite { get { throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); } } - /// - public override long Length { get { throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); } } - /// - public override long Position { get { throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); } set { throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); } } - /// - public override void Flush() { throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); } - /// - public override int Read(byte[] buffer, int offset, int count) { throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); } - /// - public override long Seek(long offset, System.IO.SeekOrigin origin) { throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); } - /// - public override void SetLength(long value) { throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); } - /// - public override void Write(byte[] buffer, int offset, int count) { throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 1f7cb869b1..f4f51f9b56 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -9,14 +9,15 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Net.Http.Headers; -using System.Runtime.CompilerServices; using System.Security; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Transactions; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; using Microsoft.Identity.Client; namespace Microsoft.Data.SqlClient @@ -105,14 +106,13 @@ internal sealed class SqlInternalConnectionTds : SqlInternalConnection, IDisposa { // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/retry-after#simple-retry-for-errors-with-http-error-codes-500-600 internal const int MsalHttpRetryStatusCode = 429; + // Connection re-route limit + internal const int MaxNumberOfRedirectRoute = 10; // CONNECTION AND STATE VARIABLES private readonly SqlConnectionPoolGroupProviderInfo _poolGroupProviderInfo; // will only be null when called for ChangePassword, or creating SSE User Instance private TdsParser _parser; - // Connection re-route limit - internal const int _maxNumberOfRedirectRoute = 10; - private SqlLoginAck _loginAck; private SqlCredential _credential; private FederatedAuthenticationFeatureExtensionData _fedAuthFeatureExtensionData; @@ -185,7 +185,7 @@ internal bool IsSQLDNSRetryEnabled } } - private bool _DNSCachingBeforeRedirect = false; + private bool _dnsCachingBeforeRedirect = false; /// /// Get or set if the control ring send redirect token and feature ext ack with true for DNSCaching @@ -194,11 +194,11 @@ internal bool IsDNSCachingBeforeRedirectSupported { get { - return _DNSCachingBeforeRedirect; + return _dnsCachingBeforeRedirect; } set { - _DNSCachingBeforeRedirect = value; + _dnsCachingBeforeRedirect = value; } } @@ -207,11 +207,14 @@ internal bool IsDNSCachingBeforeRedirectSupported // Json Support Flag internal bool IsJsonSupportEnabled = false; + // Vector Support Flag + internal bool IsVectorSupportEnabled = false; + // TCE flags internal byte _tceVersionSupported; // The pool that this connection is associated with, if at all it is. - private DbConnectionPool _dbConnectionPool; + private IDbConnectionPool _dbConnectionPool; // This is used to preserve the authentication context object if we decide to cache it for subsequent connections in the same pool. // This will finally end up in _dbConnectionPool.AuthenticationContexts, but only after 1 successful login to SQL Server using this context. @@ -431,8 +434,9 @@ internal SqlConnectionTimeoutErrorInternal TimeoutErrorInternal } // OTHER STATE VARIABLES AND REFERENCES - internal Guid _clientConnectionId = Guid.Empty; + + // Routing information (ROR) private Guid _originalClientConnectionId = Guid.Empty; private string _routingDestination = null; private readonly TimeoutTimer _timeout; @@ -452,16 +456,35 @@ internal SqlInternalConnectionTds( SessionData reconnectSessionData = null, bool applyTransientFaultHandling = false, string accessToken = null, - DbConnectionPool pool = null, + IDbConnectionPool pool = null, Func> accessTokenCallback = null) : base(connectionOptions) - { #if DEBUG if (reconnectSessionData != null) { reconnectSessionData._debugReconnectDataApplied = true; } +#if NETFRAMEWORK + try + { + // use this to help validate this object is only created after the following permission has been previously demanded in the current codepath + if (userConnectionOptions != null) + { + // As mentioned above, userConnectionOptions may be different to connectionOptions, so we need to demand on the correct connection string + userConnectionOptions.DemandPermission(); + } + else + { + connectionOptions.DemandPermission(); + } + } + catch (System.Security.SecurityException) + { + System.Diagnostics.Debug.Assert(false, "unexpected SecurityException for current codepath"); + throw; + } +#endif #endif Debug.Assert(reconnectSessionData == null || connectionOptions.ConnectRetryCount > 0, "Reconnect data supplied with CR turned off"); @@ -511,6 +534,9 @@ internal SqlInternalConnectionTds( _parserLock.Wait(canReleaseFromAnyThread: false); ThreadHasParserLockForClose = true; // In case of error, let ourselves know that we already own the parser lock +#if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); +#endif try { _timeout = TimeoutTimer.StartSecondsTimeout(connectionOptions.ConnectTimeout); @@ -523,7 +549,6 @@ internal SqlInternalConnectionTds( try { OpenLoginEnlist(_timeout, connectionOptions, credential, newPassword, newSecurePassword, redirectedUserInstance); - break; } catch (SqlException sqlex) @@ -543,6 +568,21 @@ internal SqlInternalConnectionTds( } } } + catch (System.OutOfMemoryException) + { + DoomThisConnection(); + throw; + } + catch (System.StackOverflowException) + { + DoomThisConnection(); + throw; + } + catch (System.Threading.ThreadAbortException) + { + DoomThisConnection(); + throw; + } finally { ThreadHasParserLockForClose = false; @@ -696,6 +736,7 @@ protected override bool ReadyToPrepareTransaction { get { + // TODO: probably need to use a different method, but that's a different bug bool result = FindLiveReader(null) == null; // can't prepare with a live data reader... return result; } @@ -718,6 +759,16 @@ public int ServerProcessId } } + /// + /// Get boolean that specifies whether an enlisted transaction can be unbound from + /// the connection when that transaction completes. + /// + /// + /// This override always returns false. + /// + /// + /// The SqlInternalConnectionTds.CheckEnlistedTransactionBinding method handles implicit unbinding for disposed transactions. + /// protected override bool UnbindOnTransactionCompletion { get @@ -737,7 +788,7 @@ protected override bool UnbindOnTransactionCompletion [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] // copied from Triaged.cs protected override void ChangeDatabaseInternal(string database) { - // Add brackets around database + // MDAC 73598 - add brackets around database database = SqlConnection.FixupDatabaseTransactionName(database); Task executeTask = _parser.TdsExecuteSQLBatch("use " + database, ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true); Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes"); @@ -797,7 +848,7 @@ internal override void ValidateConnectionForExecute(SqlCommand command) { // if MARS is on, then a datareader associated with the command exists // or if MARS is off, then a datareader exists - throw ADP.OpenReaderExists(parser.MARSOn); + throw ADP.OpenReaderExists(parser.MARSOn); // MDAC 66411 } else if (!parser.MARSOn && parser._physicalStateObj.HasPendingData) { @@ -868,12 +919,8 @@ internal void CheckEnlistedTransactionBinding() } } - internal override bool IsConnectionAlive(bool throwOnException) - { - bool isAlive = false; - isAlive = _parser._physicalStateObj.IsConnectionAlive(throwOnException); - return isAlive; - } + internal override bool IsConnectionAlive(bool throwOnException) => + _parser._physicalStateObj.IsConnectionAlive(throwOnException); //////////////////////////////////////////////////////////////////////////////////////// // POOLING METHODS @@ -881,6 +928,10 @@ internal override bool IsConnectionAlive(bool throwOnException) protected override void Activate(Transaction transaction) { +#if NETFRAMEWORK + FailoverPermissionDemand(); // Demand for unspecified failover pooled connections +#endif + // When we're required to automatically enlist in transactions and // there is one we enlist in it. On the other hand, if there isn't a // transaction and we are currently enlisted in one, then we @@ -946,11 +997,11 @@ private void ResetConnection() if (_fResetConnection) { - // Ensure we are either going against 2000, or we are not enlisted in a - // distributed transaction - otherwise don't reset! - // Prepare the parser for the connection reset - the next time a trip - // to the server is made. - _parser.PrepareResetConnection(IsTransactionRoot && !IsNonPoolableTransactionRoot); + // Pooled connections that are enlisted in a transaction must have their transaction + // preserved when reseting the connection state. Otherwise, future uses of the connection + // from the pool will execute outside of the transaction, in auto-commit mode. + // https://github.com/dotnet/SqlClient/issues/2970 + _parser.PrepareResetConnection(EnlistedTransaction is not null && Pool is not null); // Reset dictionary values, since calling reset will not send us env_changes. CurrentDatabase = _originalDatabase; @@ -1022,8 +1073,7 @@ internal void ExecuteTransaction2005( string transactionName, System.Data.IsolationLevel iso, SqlInternalTransaction internalTransaction, - bool isDelegateControlRequest - ) + bool isDelegateControlRequest) { TdsEnums.TransactionManagerRequestType requestType = TdsEnums.TransactionManagerRequestType.Begin; TdsEnums.TransactionManagerIsolationLevel isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadCommitted; @@ -1144,7 +1194,7 @@ bool isDelegateControlRequest } } - // _parser may be nulled out during TdsExecuteTransactionManagerRequest. + // SQLBU #406778 - _parser may be nulled out during TdsExecuteTransactionManagerRequest. // Only use local variable after this call. _parser.TdsExecuteTransactionManagerRequest(null, requestType, transactionName, isoLevel, ConnectionOptions.ConnectTimeout, internalTransaction, stateObj, isDelegateControlRequest); @@ -1170,6 +1220,7 @@ bool isDelegateControlRequest internal override void DelegatedTransactionEnded() { + // TODO: I don't like the way that this works, but I don't want to rototill the entire pooler to avoid this call. base.DelegatedTransactionEnded(); } @@ -1206,6 +1257,7 @@ private void CompleteLogin(bool enlistOK) SqlClientEventSource.Log.TryTraceEvent(" {0}, Server never sent the requested federated authentication info", ObjectID); throw SQL.ParsingError(ParsingErrorState.FedAuthInfoNotReceived); } + if (!_sessionRecoveryAcknowledged) { _currentSessionData = null; @@ -1220,7 +1272,7 @@ private void CompleteLogin(bool enlistOK) _currentSessionData._initialCollation = _currentSessionData._collation; _currentSessionData._initialLanguage = _currentLanguage; } - bool isEncrypted = _parser.EncryptionOptions == EncryptionOptions.ON; + bool isEncrypted = (_parser.EncryptionOptions & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.ON; if (_recoverySessionData != null) { if (_recoverySessionData._encrypted != isEncrypted) @@ -1373,9 +1425,11 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, requestedFeatures |= TdsEnums.FeatureExtension.AzureSQLSupport; } - // The SQLDNSCaching feature is implicitly set + // The following features are implicitly set requestedFeatures |= TdsEnums.FeatureExtension.SQLDNSCaching; requestedFeatures |= TdsEnums.FeatureExtension.JsonSupport; + requestedFeatures |= TdsEnums.FeatureExtension.VectorSupport; + _parser.TdsLogin(login, requestedFeatures, _recoverySessionData, _fedAuthFeatureExtensionData, encrypt); } @@ -1514,23 +1568,29 @@ private void LoginNoFailover(ServerInfo serverInfo, Debug.Assert(object.ReferenceEquals(connectionOptions, this.ConnectionOptions), "ConnectionOptions argument and property must be the same"); // consider removing the argument int routingAttempts = 0; ServerInfo originalServerInfo = serverInfo; // serverInfo may end up pointing to new object due to routing, original object is used to set CurrentDatasource + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, host={1}", ObjectID, serverInfo.UserServerName); + int sleepInterval = 100; //milliseconds to sleep (back off) between attempts. ResolveExtendedServerName(serverInfo, !redirectedUserInstance, connectionOptions); long timeoutUnitInterval = 0; - if (connectionOptions.MultiSubnetFailover) + bool isParallel = connectionOptions.MultiSubnetFailover; + + if (isParallel) { + float failoverTimeoutStep = ADP.FailoverTimeoutStep; + // Determine unit interval if (timeout.IsInfinite) { - timeoutUnitInterval = checked((long)(ADP.FailoverTimeoutStep * (1000L * ADP.DefaultConnectionTimeout))); + timeoutUnitInterval = checked((long)(failoverTimeoutStep * (1000L * ADP.DefaultConnectionTimeout))); } else { - timeoutUnitInterval = checked((long)(ADP.FailoverTimeoutStep * timeout.MillisecondsRemaining)); + timeoutUnitInterval = checked((long)(failoverTimeoutStep * timeout.MillisecondsRemaining)); } } // Only three ways out of this loop: @@ -1544,14 +1604,18 @@ private void LoginNoFailover(ServerInfo serverInfo, // back into the parser for the error cases. int attemptNumber = 0; TimeoutTimer intervalTimer = null; + TimeoutTimer attemptOneLoginTimeout = timeout; + while (true) { - if (connectionOptions.MultiSubnetFailover) + if (isParallel) { - attemptNumber++; + int multiplier = ++attemptNumber; + // Set timeout for this attempt, but don't exceed original timer - long nextTimeoutInterval = checked(timeoutUnitInterval * attemptNumber); + long nextTimeoutInterval = checked(timeoutUnitInterval * multiplier); long milliseconds = timeout.MillisecondsRemaining; + if (nextTimeoutInterval > milliseconds) { nextTimeoutInterval = milliseconds; @@ -1569,10 +1633,15 @@ private void LoginNoFailover(ServerInfo serverInfo, try { + if (isParallel) + { + attemptOneLoginTimeout = intervalTimer; + } + AttemptOneLogin(serverInfo, newPassword, newSecurePassword, - connectionOptions.MultiSubnetFailover ? intervalTimer : timeout); + attemptOneLoginTimeout); if (connectionOptions.MultiSubnetFailover && ServerProvidedFailOverPartner != null) { @@ -1583,9 +1652,9 @@ private void LoginNoFailover(ServerInfo serverInfo, if (RoutingInfo != null) { SqlClientEventSource.Log.TryTraceEvent(" Routed to {0}", serverInfo.ExtendedServerName); - if (routingAttempts > _maxNumberOfRedirectRoute) + if (routingAttempts > MaxNumberOfRedirectRoute) { - throw SQL.ROR_RecursiveRoutingNotSupported(this, _maxNumberOfRedirectRoute); + throw SQL.ROR_RecursiveRoutingNotSupported(this, MaxNumberOfRedirectRoute); } if (timeout.IsExpired) @@ -1757,6 +1826,7 @@ TimeoutTimer timeout } // Initialize loop variables + bool failoverDemandDone = false; // have we demanded for partner information yet (as necessary)? int attemptNumber = 0; // Only three ways out of this loop: @@ -1791,6 +1861,14 @@ TimeoutTimer timeout ServerInfo currentServerInfo; if (useFailoverHost) { + if (!failoverDemandDone) + { +#if NETFRAMEWORK + FailoverPermissionDemand(); +#endif + failoverDemandDone = true; + } + // Primary server may give us a different failover partner than the connection string indicates. Update it if (ServerProvidedFailOverPartner != null && failoverServerInfo.ResolvedServerName != ServerProvidedFailOverPartner) { @@ -1816,18 +1894,19 @@ TimeoutTimer timeout intervalTimer, withFailover: true ); - int routingAttemps = 0; + + int routingAttempts = 0; while (RoutingInfo != null) { - if (routingAttemps > _maxNumberOfRedirectRoute) + if (routingAttempts > MaxNumberOfRedirectRoute) { - throw SQL.ROR_RecursiveRoutingNotSupported(this, _maxNumberOfRedirectRoute); + throw SQL.ROR_RecursiveRoutingNotSupported(this, MaxNumberOfRedirectRoute); } - routingAttemps++; + routingAttempts++; SqlClientEventSource.Log.TryTraceEvent(" Routed to {0}", RoutingInfo.ServerName); - if(_parser != null) + if (_parser != null) { _parser.Disconnect(); } @@ -1841,7 +1920,7 @@ TimeoutTimer timeout _originalClientConnectionId = _clientConnectionId; _routingDestination = currentServerInfo.UserServerName; - // restore properties that could be changed by the environemnt tokens + // restore properties that could be changed by the environment tokens _currentPacketSize = connectionOptions.PacketSize; _currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage; CurrentDatabase = _originalDatabase = connectionOptions.InitialCatalog; @@ -1855,6 +1934,7 @@ TimeoutTimer timeout intervalTimer, withFailover: true); } + break; // leave the while loop -- we've successfully connected } catch (SqlException sqlex) @@ -1965,12 +2045,11 @@ private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup, } // Common code path for making one attempt to establish a connection and log in to server. - private void AttemptOneLogin( - ServerInfo serverInfo, - string newPassword, - SecureString newSecurePassword, - TimeoutTimer timeout, - bool withFailover = false) + private void AttemptOneLogin(ServerInfo serverInfo, + string newPassword, + SecureString newSecurePassword, + TimeoutTimer timeout, + bool withFailover = false) { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, timeout={1}[msec], server={2}", ObjectID, timeout.MillisecondsRemaining, serverInfo.ExtendedServerName); RoutingInfo = null; // forget routing information @@ -1997,7 +2076,15 @@ private void AttemptOneLogin( _timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin); } - +#if NETFRAMEWORK + internal void FailoverPermissionDemand() + { + if (PoolGroupProviderInfo != null) + { + PoolGroupProviderInfo.FailoverPermissionDemand(); + } + } +#endif //////////////////////////////////////////////////////////////////////////////////////// // PREPARED COMMAND METHODS @@ -2040,18 +2127,39 @@ internal bool GetSessionAndReconnectIfNeeded(SqlConnection parent, int timeout = try { - Task reconnectTask = parent.ValidateAndReconnect(() => +#if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); +#endif + try { - ThreadHasParserLockForClose = false; - _parserLock.Release(); - releaseConnectionLock = false; - }, timeout); - if (reconnectTask != null) + Task reconnectTask = parent.ValidateAndReconnect(() => + { + ThreadHasParserLockForClose = false; + _parserLock.Release(); + releaseConnectionLock = false; + }, timeout); + if (reconnectTask != null) + { + AsyncHelper.WaitForCompletion(reconnectTask, timeout); + return true; + } + return false; + } + catch (System.OutOfMemoryException) { - AsyncHelper.WaitForCompletion(reconnectTask, timeout); - return true; + DoomThisConnection(); + throw; + } + catch (System.StackOverflowException) + { + DoomThisConnection(); + throw; + } + catch (System.Threading.ThreadAbortException) + { + DoomThisConnection(); + throw; } - return false; } finally { @@ -2069,7 +2177,7 @@ internal bool GetSessionAndReconnectIfNeeded(SqlConnection parent, int timeout = internal void BreakConnection() { - var connection = Connection; + SqlConnection connection = Connection; SqlClientEventSource.Log.TryTraceEvent(" {0}, Breaking connection.", ObjectID); DoomThisConnection(); // Mark connection as unusable, so it will be destroyed if (connection != null) @@ -2234,7 +2342,6 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) // We want to refresh the token, if taking the lock on the authentication context is successful. bool attemptRefreshTokenLocked = false; - if (_dbConnectionPool != null) { Debug.Assert(_dbConnectionPool.AuthenticationContexts != null); @@ -2290,9 +2397,9 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) // If the lock could not be obtained, it will return false, without attempting to fetch a new token. attemptRefreshTokenLocked = TryGetFedAuthTokenLocked(fedAuthInfo, dbConnectionPoolAuthenticationContext, out _fedAuthToken); - // If TryGetFedAuthTokenLocked returns true, it means lock was obtained and fedAuthToken should not be null. + // If TryGetFedAuthTokenLocked returns true, it means lock was obtained and _fedAuthToken should not be null. // If there was an exception in retrieving the new token, TryGetFedAuthTokenLocked should have thrown, so we won't be here. - Debug.Assert(!attemptRefreshTokenLocked || _fedAuthToken != null, "Either Lock should not have been obtained or fedAuthToken should not be null."); + Debug.Assert(!attemptRefreshTokenLocked || _fedAuthToken != null, "Either Lock should not have been obtained or _fedAuthToken should not be null."); Debug.Assert(!attemptRefreshTokenLocked || _newDbConnectionPoolAuthenticationContext != null, "Either Lock should not have been obtained or _newDbConnectionPoolAuthenticationContext should not be null."); // Indicate in EventSource Trace that we are successful with the update. @@ -2310,7 +2417,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) { // Get the Federated Authentication Token. _fedAuthToken = GetFedAuthToken(fedAuthInfo); - Debug.Assert(_fedAuthToken != null, "fedAuthToken should not be null."); + Debug.Assert(_fedAuthToken != null, "_fedAuthToken should not be null."); if (_dbConnectionPool != null) { @@ -2326,7 +2433,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) else if (!attemptRefreshTokenLocked) { Debug.Assert(dbConnectionPoolAuthenticationContext != null, "dbConnectionPoolAuthenticationContext should not be null."); - Debug.Assert(_fedAuthToken == null, "fedAuthToken should be null in this case."); + Debug.Assert(_fedAuthToken == null, "_fedAuthToken should be null in this case."); Debug.Assert(_newDbConnectionPoolAuthenticationContext == null, "_newDbConnectionPoolAuthenticationContext should be null."); _fedAuthToken = new SqlFedAuthToken(); @@ -2337,7 +2444,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) _fedAuthToken.expirationFileTime = dbConnectionPoolAuthenticationContext.ExpirationTime.ToFileTime(); } - Debug.Assert(_fedAuthToken != null && _fedAuthToken.accessToken != null, "fedAuthToken and fedAuthToken.accessToken cannot be null."); + Debug.Assert(_fedAuthToken != null && _fedAuthToken.accessToken != null, "_fedAuthToken and _fedAuthToken.accessToken cannot be null."); _parser.SendFedAuthToken(_fedAuthToken); } @@ -2422,7 +2529,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) // Username to use in error messages. string username = null; - var authProvider = SqlAuthenticationProvider.GetProvider(ConnectionOptions.Authentication); + SqlAuthenticationProvider authProvider = SqlAuthenticationProvider.GetProvider(ConnectionOptions.Authentication); if (authProvider == null && _accessTokenCallback == null) throw SQL.CannotFindAuthProvider(ConnectionOptions.Authentication.ToString()); @@ -2596,14 +2703,8 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) throw ADP.CreateSqlException(msalException, ConnectionOptions, this, username); } - SqlClientEventSource.Log.TryAdvancedTraceEvent( - " {0}, sleeping {1}[Milliseconds]", - ObjectID, - sleepInterval); - SqlClientEventSource.Log.TryAdvancedTraceEvent( - " {0}, remaining {1}[Milliseconds]", - ObjectID, - _timeout.MillisecondsRemaining); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, sleeping {1}[Milliseconds]", ObjectID, sleepInterval); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, remaining {1}[Milliseconds]", ObjectID, _timeout.MillisecondsRemaining); Thread.Sleep(sleepInterval); sleepInterval *= 2; @@ -2644,7 +2745,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) internal void OnFeatureExtAck(int featureId, byte[] data) { - if (RoutingInfo != null && TdsEnums.FEATUREEXT_SQLDNSCACHING != featureId) + if (RoutingInfo != null && featureId != TdsEnums.FEATUREEXT_SQLDNSCACHING) { return; } @@ -2704,6 +2805,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data) case TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS: { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for GlobalTransactions", ObjectID); + if (data.Length < 1) { SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown version number for GlobalTransactions", ObjectID); @@ -2717,9 +2819,11 @@ internal void OnFeatureExtAck(int featureId, byte[] data) } break; } + case TdsEnums.FEATUREEXT_FEDAUTH: { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for federated authentication", ObjectID); + if (!_federatedAuthenticationRequested) { SqlClientEventSource.Log.TryTraceEvent(" {0}, Did not request federated authentication", ObjectID); @@ -2742,6 +2846,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data) default: Debug.Fail("Unknown _fedAuthLibrary type"); + SqlClientEventSource.Log.TryTraceEvent(" {0}, Attempting to use unknown federated authentication library", ObjectID); throw SQL.ParsingErrorLibraryType(ParsingErrorState.FedAuthFeatureAckUnknownLibraryType, (int)_fedAuthFeatureExtensionData.libraryType); } @@ -2769,7 +2874,6 @@ internal void OnFeatureExtAck(int featureId, byte[] data) } #endif } - break; } case TdsEnums.FEATUREEXT_TCE: @@ -2801,6 +2905,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data) } break; } + case TdsEnums.FEATUREEXT_AZURESQLSUPPORT: { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for AzureSQLSupport", ObjectID); @@ -2816,10 +2921,10 @@ internal void OnFeatureExtAck(int featureId, byte[] data) if ((data[0] & 1) == 1 && SqlClientEventSource.Log.IsTraceEnabled()) { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, FailoverPartner enabled with Readonly intent for AzureSQL DB", ObjectID); - } break; } + case TdsEnums.FEATUREEXT_DATACLASSIFICATION: { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for DATACLASSIFICATION", ObjectID); @@ -2827,7 +2932,6 @@ internal void OnFeatureExtAck(int featureId, byte[] data) if (data.Length < 1) { SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown token for DATACLASSIFICATION", ObjectID); - throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); } byte supportedDataClassificationVersion = data[0]; @@ -2840,13 +2944,13 @@ internal void OnFeatureExtAck(int featureId, byte[] data) if (data.Length != 2) { SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown token for DATACLASSIFICATION", ObjectID); - throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); } byte enabled = data[1]; _parser.DataClassificationVersion = (enabled == 0) ? TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED : supportedDataClassificationVersion; break; } + case TdsEnums.FEATUREEXT_UTF8SUPPORT: { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for UTF8 support", ObjectID); @@ -2854,7 +2958,6 @@ internal void OnFeatureExtAck(int featureId, byte[] data) if (data.Length < 1) { SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown value for UTF8 support", ObjectID); - throw SQL.ParsingError(); } break; @@ -2891,7 +2994,6 @@ internal void OnFeatureExtAck(int featureId, byte[] data) // get IPv4 + IPv6 + Port number // not put them in the DNS cache at this point but need to store them somewhere // generate pendingSQLDNSObject and turn on IsSQLDNSRetryEnabled flag - break; } @@ -2913,6 +3015,24 @@ internal void OnFeatureExtAck(int featureId, byte[] data) break; } + case TdsEnums.FEATUREEXT_VECTORSUPPORT: + { + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for VECTORSUPPORT", ObjectID); + if (data.Length != 1) + { + SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown token for VECTORSUPPORT", ObjectID); + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); + } + byte vectorSupportVersion = data[0]; + if (vectorSupportVersion == 0 || vectorSupportVersion > TdsEnums.MAX_SUPPORTED_VECTOR_VERSION) + { + SqlClientEventSource.Log.TryTraceEvent(" {0}, Invalid version number {1} for VECTORSUPPORT, Max supported version is {2}", ObjectID, vectorSupportVersion, TdsEnums.MAX_SUPPORTED_VECTOR_VERSION); + throw SQL.ParsingError(); + } + IsVectorSupportEnabled = true; + break; + } + default: { // Unknown feature ack diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlTransaction.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlTransaction.cs deleted file mode 100644 index 50abbf30b2..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlTransaction.cs +++ /dev/null @@ -1,174 +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.ComponentModel; -using System.Data.Common; -using Microsoft.Data.Common; - -namespace Microsoft.Data.SqlClient -{ - /// - public sealed partial class SqlTransaction : DbTransaction - { - private static readonly SqlDiagnosticListener s_diagnosticListener = new(SqlClientDiagnosticListenerExtensions.DiagnosticListenerName); - - //////////////////////////////////////////////////////////////////////////////////////// - // PUBLIC METHODS - //////////////////////////////////////////////////////////////////////////////////////// - - /// - public override void Commit() - { - using (DiagnosticTransactionScope diagnosticScope = s_diagnosticListener.CreateTransactionCommitScope(_isolationLevel, _connection, InternalTransaction)) - { - ZombieCheck(); - - using (TryEventScope.Create("SqlTransaction.Commit | API | Object Id {0}", ObjectID)) - { - SqlStatistics statistics = null; - SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlTransaction.Commit | API | Correlation | Object Id {0}, Activity Id {1}, Client Connection Id {2}", ObjectID, ActivityCorrelator.Current, Connection?.ClientConnectionId); - try - { - statistics = SqlStatistics.StartTimer(Statistics); - - _isFromAPI = true; - - _internalTransaction.Commit(); - } - catch (SqlException ex) - { - diagnosticScope.SetException(ex); - // GitHub Issue #130 - When a timeout exception has occurred on transaction completion request, - // this connection may not be in reusable state. - // We will abort this connection and make sure it does not go back to the pool. - if (ex.InnerException is Win32Exception innerException && innerException.NativeErrorCode == TdsEnums.SNI_WAIT_TIMEOUT) - { - _connection.Abort(ex); - } - throw; - } - catch (Exception ex) - { - diagnosticScope.SetException(ex); - throw; - } - finally - { - SqlStatistics.StopTimer(statistics); - _isFromAPI = false; - } - } - } - } - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - if (!IsZombied && !Is2005PartialZombie) - { - _internalTransaction.Dispose(); - } - } - base.Dispose(disposing); - } - - /// - public override void Rollback() - { - using (DiagnosticTransactionScope diagnosticScope = s_diagnosticListener.CreateTransactionRollbackScope(_isolationLevel, _connection, InternalTransaction, null)) - { - if (Is2005PartialZombie) - { - // Put something in the trace in case a customer has an issue - SqlClientEventSource.Log.TryAdvancedTraceEvent("SqlTransaction.Rollback | ADV | Object Id {0}, partial zombie no rollback required", ObjectID); - _internalTransaction = null; // 2005 zombification - } - else - { - ZombieCheck(); - - SqlStatistics statistics = null; - using (TryEventScope.Create("SqlTransaction.Rollback | API | Object Id {0}", ObjectID)) - { - SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlTransaction.Rollback | API | Correlation | Object Id {0}, ActivityID {1}, Client Connection Id {2}", ObjectID, ActivityCorrelator.Current, Connection?.ClientConnectionId); - try - { - statistics = SqlStatistics.StartTimer(Statistics); - - _isFromAPI = true; - - _internalTransaction.Rollback(); - } - catch (Exception ex) - { - diagnosticScope.SetException(ex); - throw; - } - finally - { - SqlStatistics.StopTimer(statistics); - _isFromAPI = false; - } - } - } - } - } - - /// - public override void Rollback(string transactionName) - { - using (DiagnosticTransactionScope diagnosticScope = s_diagnosticListener.CreateTransactionRollbackScope(_isolationLevel, _connection, InternalTransaction, transactionName)) - { - ZombieCheck(); - - using (TryEventScope.Create(SqlClientEventSource.Log.TryScopeEnterEvent("SqlTransaction.Rollback | API | Object Id {0}, Transaction Name='{1}', ActivityID {2}, Client Connection Id {3}", ObjectID, transactionName, ActivityCorrelator.Current, Connection?.ClientConnectionId))) - { - SqlStatistics statistics = null; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - - _isFromAPI = true; - - _internalTransaction.Rollback(transactionName); - } - catch (Exception ex) - { - diagnosticScope.SetException(ex); - throw; - } - finally - { - SqlStatistics.StopTimer(statistics); - _isFromAPI = false; - } - } - } - } - - /// - public override void Save(string savePointName) - { - ZombieCheck(); - - SqlStatistics statistics = null; - using (TryEventScope.Create("SqlTransaction.Save | API | Object Id {0} | Save Point Name '{1}'", ObjectID, savePointName)) - { - try - { - statistics = SqlStatistics.StartTimer(Statistics); - - _internalTransaction.Save(savePointName); - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Unix.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Unix.cs index 2f242a083f..3944de7915 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Unix.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Unix.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.Data.SqlClient.SNI; +using Microsoft.Data.SqlClient.ManagedSni; namespace Microsoft.Data.SqlClient { @@ -23,20 +23,18 @@ private void WaitForSSLHandShakeToComplete(ref uint error, ref int protocolVersi // No - Op } - private SNIErrorDetails GetSniErrorDetails() + private TdsParserStateObject.SniErrorDetails GetSniErrorDetails() { - SNIErrorDetails details; - SNIError sniError = SNIProxy.Instance.GetLastError(); - details.sniErrorNumber = sniError.sniError; - details.errorMessage = sniError.errorMessage; - details.nativeError = sniError.nativeError; - details.provider = (int)sniError.provider; - details.lineNumber = sniError.lineNumber; - details.function = sniError.function; - details.exception = sniError.exception; - - return details; - } + SniError sniError = SniProxy.Instance.GetLastError(); + return new( + sniError.errorMessage, + sniError.nativeError, + sniError.sniError, + (int)sniError.provider, + sniError.lineNumber, + sniError.function, + sniError.exception); + } } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs index 480eb8dc97..7576efe32a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.Windows.cs @@ -5,7 +5,8 @@ using System; using System.Diagnostics; using Interop.Windows.Sni; -using Microsoft.Data.SqlClient.SNI; +using Microsoft.Data.SqlClient.ManagedSni; +using SniError = Microsoft.Data.SqlClient.ManagedSni.SniError; namespace Microsoft.Data.SqlClient { @@ -58,32 +59,31 @@ private void WaitForSSLHandShakeToComplete(ref uint error, ref int protocolVersi } } - private SNIErrorDetails GetSniErrorDetails() + private TdsParserStateObject.SniErrorDetails GetSniErrorDetails() { - SNIErrorDetails details = new SNIErrorDetails(); - if (TdsParserStateObjectFactory.UseManagedSNI) { - SNIError sniError = SNIProxy.Instance.GetLastError(); - details.sniErrorNumber = sniError.sniError; - details.errorMessage = sniError.errorMessage; - details.nativeError = sniError.nativeError; - details.provider = (int)sniError.provider; - details.lineNumber = sniError.lineNumber; - details.function = sniError.function; - details.exception = sniError.exception; + SniError sniError = SniProxy.Instance.GetLastError(); + return new( + sniError.errorMessage, + sniError.nativeError, + sniError.sniError, + (int)sniError.provider, + sniError.lineNumber, + sniError.function, + sniError.exception); } else { - SNINativeMethodWrapper.SNIGetLastError(out SniError sniError); - details.sniErrorNumber = sniError.sniError; - details.errorMessage = sniError.errorMessage; - details.nativeError = sniError.nativeError; - details.provider = (int)sniError.provider; - details.lineNumber = sniError.lineNumber; - details.function = sniError.function; + SniNativeWrapper.SniGetLastError(out Interop.Windows.Sni.SniError sniError); + return new( + sniError.errorMessage, + sniError.nativeError, + sniError.sniError, + (int)sniError.provider, + sniError.lineNumber, + sniError.function); } - return details; } } // tdsparser diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index bb9008f017..c020e7bb3e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -11,62 +11,40 @@ using System.Diagnostics; using System.Globalization; using System.IO; +#if NET using System.Security.Authentication; +#else +using System.Runtime.CompilerServices; +#endif using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; using Interop.Common.Sni; +#if NETFRAMEWORK +using Interop.Windows.Sni; +#endif using Microsoft.Data.Common; using Microsoft.Data.ProviderBase; using Microsoft.Data.Sql; using Microsoft.Data.SqlClient.DataClassification; +using Microsoft.Data.SqlClient.LocalDb; using Microsoft.Data.SqlClient.Server; +#if NETFRAMEWORK using Microsoft.Data.SqlTypes; +#endif +using Microsoft.SqlServer.Server; namespace Microsoft.Data.SqlClient { - - internal struct SNIErrorDetails - { - public string errorMessage; - public uint nativeError; - public uint sniErrorNumber; - public int provider; - public uint lineNumber; - public string function; - public Exception exception; - } - // The TdsParser Object controls reading/writing to the netlib, parsing the tds, // and surfacing objects to the user. internal sealed partial class TdsParser { - internal struct ReliabilitySection - { - /// - /// This is a no-op in netcore version. Only needed for merging with netfx codebase. - /// - [Conditional("NETFRAMEWORK")] - internal static void Assert(string message) - { - } - - [Conditional("NETFRAMEWORK")] - internal void Start() - { - } - - [Conditional("NETFRAMEWORK")] - internal void Stop() - { - } - } - private static int _objectTypeCount; // EventSource counter private readonly SqlClientLogger _logger = new SqlClientLogger(); - private SSPIContextProvider _authenticationProvider; + private SspiContextProvider _authenticationProvider; internal readonly int _objectID = Interlocked.Increment(ref _objectTypeCount); internal int ObjectID => _objectID; @@ -137,17 +115,12 @@ internal void Stop() // Version variables - private bool _is2005 = false; // set to true if speaking to 2005 or later - private bool _is2008 = false; private bool _is2012 = false; private bool _is2022 = false; - private byte[][] _sniSpnBuffer = null; - // UNDONE - need to have some for both instances - both command and default??? - // SqlStatistics private SqlStatistics _statistics = null; @@ -286,8 +259,6 @@ internal EncryptionOptions EncryptionOptions } } - internal bool Is2005OrNewer => true; - internal bool Is2008OrNewer { get @@ -385,8 +356,7 @@ internal void ProcessPendingAck(TdsParserStateObject stateObj) } } - internal void Connect( - ServerInfo serverInfo, + internal void Connect(ServerInfo serverInfo, SqlInternalConnectionTds connHandler, TimeoutTimer timeout, SqlConnectionString connectionOptions, @@ -423,7 +393,6 @@ internal void Connect( } else { - _sniSpnBuffer = null; SqlClientEventSource.Log.TryTraceEvent("TdsParser.Connect | SEC | Connection Object Id {0}, Authentication Mode: {1}", _connHandler.ObjectID, authType == SqlAuthenticationMethod.NotSpecified ? SqlAuthenticationMethod.SqlPassword.ToString() : authType.ToString()); } @@ -435,13 +404,12 @@ internal void Connect( SqlClientEventSource.Log.TryTraceEvent(" Encryption will be disabled as target server is a SQL Local DB instance."); } - _sniSpnBuffer = null; _authenticationProvider = null; // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server if (integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { - _authenticationProvider = _physicalStateObj.CreateSSPIContextProvider(); + _authenticationProvider = _physicalStateObj.CreateSspiContextProvider(); SqlClientEventSource.Log.TryTraceEvent("TdsParser.Connect | SEC | SSPI or Active Directory Authentication Library loaded for SQL Server based integrated authentication"); } @@ -474,7 +442,7 @@ internal void Connect( serverInfo.ExtendedServerName, timeout, out instanceName, - ref _sniSpnBuffer, + out var resolvedServerSpn, false, true, fParallel, @@ -487,8 +455,6 @@ internal void Connect( hostNameInCertificate, serverCertificateFilename); - _authenticationProvider?.Initialize(serverInfo, _physicalStateObj, this); - if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); @@ -572,8 +538,9 @@ internal void Connect( _physicalStateObj.SniContext = SniContext.Snix_Connect; _physicalStateObj.CreatePhysicalSNIHandle( serverInfo.ExtendedServerName, - timeout, out instanceName, - ref _sniSpnBuffer, + timeout, + out instanceName, + out resolvedServerSpn, true, true, fParallel, @@ -586,8 +553,6 @@ internal void Connect( hostNameInCertificate, serverCertificateFilename); - _authenticationProvider?.Initialize(serverInfo, _physicalStateObj, this); - if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); @@ -626,6 +591,11 @@ internal void Connect( } SqlClientEventSource.Log.TryTraceEvent(" Prelogin handshake successful"); + if (_authenticationProvider is { }) + { + _authenticationProvider.Initialize(serverInfo, _physicalStateObj, this, resolvedServerSpn.Primary, resolvedServerSpn.Secondary); + } + if (_fMARS && marsCapable) { // if user explicitly disables mars or mars not supported, don't create the session pool @@ -771,7 +741,7 @@ private void SendPreLoginHandshake( // UNDONE - need to do some length verification to ensure packet does not // get too big!!! Not beyond it's max length! - + for (int option = (int)PreLoginOptions.VERSION; option < (int)PreLoginOptions.NUMOPT; option++) { int optionDataSize = 0; @@ -936,7 +906,7 @@ private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integ string warningMessage = protocol.GetProtocolWarning(); if (!string.IsNullOrEmpty(warningMessage)) { - if (!encrypt && LocalAppContextSwitches.SuppressInsecureTLSWarning) + if (!encrypt && LocalAppContextSwitches.SuppressInsecureTlsWarning) { // Skip console warning SqlClientEventSource.Log.TryTraceEvent("{3}", nameof(TdsParser), nameof(EnableSsl), SqlClientLogger.LogLevel.Warning, warningMessage); @@ -959,12 +929,11 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( out bool marsCapable, out bool fedAuthRequired, bool tlsFirst, - string serverCert) + string serverCertificateFilename) { // Assign default values - marsCapable = _fMARS; + marsCapable = _fMARS; fedAuthRequired = false; - bool is2005OrLater = false; Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); TdsOperationStatus result = _physicalStateObj.TryReadNetworkPacket(); if (result != TdsOperationStatus.Done) @@ -1023,13 +992,6 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( byte minorVersion = payload[payloadOffset + 1]; int level = (payload[payloadOffset + 2] << 8) | payload[payloadOffset + 3]; - - is2005OrLater = majorVersion >= 9; - if (!is2005OrLater) - { - marsCapable = false; // If pre-2005, MARS not supported. - } - break; case (int)PreLoginOptions.ENCRYPT: @@ -1050,7 +1012,8 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( ON, NOT_SUP, REQ, - LOGIN + LOGIN, + OPTIONS_MASK = 0x3f } */ // Any response other than NOT_SUP means the server supports encryption. @@ -1187,9 +1150,9 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) || (_connHandler._accessTokenInBytes != null && !trustServerCert); uint info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0) - | (is2005OrLater ? TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE : 0); + | TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE; - EnableSsl(info, encrypt, integratedSecurity, serverCert); + EnableSsl(info, encrypt, integratedSecurity, serverCertificateFilename); } return PreLoginHandshakeStatus.Successful; @@ -1484,12 +1447,12 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) SqlClientEventSource.Log.TryTraceEvent(" SNIContext must not be None = {0}, _fMARS = {1}, TDS Parser State = {2}", stateObj.DebugOnlyCopyOfSniContext, _fMARS, _state); #endif - SNIErrorDetails details = GetSniErrorDetails(); + TdsParserStateObject.SniErrorDetails details = GetSniErrorDetails(); - if (details.sniErrorNumber != 0) + if (details.SniErrorNumber != 0) { // handle special SNI error codes that are converted into exception which is not a SqlException. - switch (details.sniErrorNumber) + switch (details.SniErrorNumber) { case SniErrors.MultiSubnetFailoverWithMoreThan64IPs: // Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported. @@ -1510,8 +1473,8 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) } // PInvoke code automatically sets the length of the string for us // So no need to look for \0 - string errorMessage = details.errorMessage; - SqlClientEventSource.Log.TryAdvancedTraceEvent("< sc.TdsParser.ProcessSNIError |ERR|ADV > Error message Detail: {0}", details.errorMessage); + string errorMessage = details.ErrorMessage; + SqlClientEventSource.Log.TryAdvancedTraceEvent("< sc.TdsParser.ProcessSNIError |ERR|ADV > Error message Detail: {0}", details.ErrorMessage); /* Format SNI errors and add Context Information * @@ -1528,22 +1491,23 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) if (TdsParserStateObjectFactory.UseManagedSNI) { - Debug.Assert(!string.IsNullOrEmpty(details.errorMessage) || details.sniErrorNumber != 0, "Empty error message received from SNI"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Empty error message received from SNI. Error Message = {0}, SNI Error Number ={1}", details.errorMessage, details.sniErrorNumber); + Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage) || details.SniErrorNumber != 0, "Empty error message received from SNI"); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Empty error message received from SNI. Error Message = {0}, SNI Error Number ={1}", details.ErrorMessage, details.SniErrorNumber); } else { - Debug.Assert(!string.IsNullOrEmpty(details.errorMessage), "Empty error message received from SNI"); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Empty error message received from SNI. Error Message = {0}", details.errorMessage); + Debug.Assert(!string.IsNullOrEmpty(details.ErrorMessage), "Empty error message received from SNI"); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Empty error message received from SNI. Error Message = {0}", details.ErrorMessage); } string sqlContextInfo = StringsHelper.GetResourceString(stateObj.SniContext.ToString()); - string providerRid = string.Format("SNI_PN{0}", details.provider); + string providerRid = string.Format("SNI_PN{0}", details.Provider); string providerName = StringsHelper.GetResourceString(providerRid); Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'"); - uint win32ErrorCode = details.nativeError; + int win32ErrorCode = details.NativeError; + SqlClientEventSource.Log.TryAdvancedTraceEvent(" SNI Native Error Code = {0}", win32ErrorCode); - if (details.sniErrorNumber == 0) + if (details.SniErrorNumber == 0) { // Provider error. The message from provider is preceeded with non-localizable info from SNI // strip provider info from SNI @@ -1577,7 +1541,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) if (TdsParserStateObjectFactory.UseManagedSNI) { // SNI error. Append additional error message info if available and hasn't been included. - string sniLookupMessage = SQL.GetSNIErrorMessage(details.sniErrorNumber); + string sniLookupMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber); errorMessage = (string.IsNullOrEmpty(errorMessage) || errorMessage.Contains(sniLookupMessage)) ? sniLookupMessage : (sniLookupMessage + ": " + errorMessage); @@ -1585,25 +1549,25 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) else { // SNI error. Replace the entire message. - errorMessage = SQL.GetSNIErrorMessage(details.sniErrorNumber); + errorMessage = SQL.GetSNIErrorMessage(details.SniErrorNumber); // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code - if (details.sniErrorNumber == SniErrors.LocalDBErrorCode) + if (details.SniErrorNumber == SniErrors.LocalDBErrorCode) { - errorMessage += LocalDBAPI.GetLocalDBMessage((int)details.nativeError); + errorMessage += LocalDbApi.GetLocalDbMessage(details.NativeError); win32ErrorCode = 0; } SqlClientEventSource.Log.TryAdvancedTraceEvent(" Extracting the latest exception from native SNI. errorMessage: {0}", errorMessage); } } errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})", - sqlContextInfo, providerName, (int)details.sniErrorNumber, errorMessage); + sqlContextInfo, providerName, (int)details.SniErrorNumber, errorMessage); SqlClientEventSource.Log.TryAdvancedTraceErrorEvent(" SNI Error Message. Native Error = {0}, Line Number ={1}, Function ={2}, Exception ={3}, Server = {4}", - (int)details.nativeError, (int)details.lineNumber, details.function, details.exception, _server); + details.NativeError, (int)details.LineNumber, details.Function, details.Exception, _server); - return new SqlError(infoNumber: (int)details.nativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server, - errorMessage, details.function, (int)details.lineNumber, win32ErrorCode: details.nativeError, details.exception); + return new SqlError(infoNumber: details.NativeError, errorState: 0x00, TdsEnums.FATAL_ERROR_CLASS, _server, + errorMessage, details.Function, (int)details.LineNumber, win32ErrorCode: details.NativeError, details.Exception); } } @@ -1959,8 +1923,6 @@ internal void PrepareResetConnection(bool preserveTransaction) _fPreserveTransaction = preserveTransaction; } - - internal bool Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) { bool syncOverAsync = stateObj._syncOverAsync; @@ -2045,7 +2007,9 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle } if (TdsParserState.Broken == State || TdsParserState.Closed == State) + { break; // jump out of the loop if the state is already broken or closed. + } if (!stateObj._accumulateInfoEvents && (stateObj._pendingInfoEvents != null)) { @@ -2065,10 +2029,13 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle } } else + { foreach (SqlError error in stateObj._pendingInfoEvents) { stateObj.AddWarning(error); } + } + } stateObj._pendingInfoEvents = null; } @@ -2203,7 +2170,7 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle dataStream.BrowseModeInfoConsumed = true; } else - { + { // no dataStream result = stateObj.TrySkipBytes(tokenLength); if (result != TdsOperationStatus.Done) @@ -2217,7 +2184,7 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle case TdsEnums.SQLDONE: case TdsEnums.SQLDONEPROC: case TdsEnums.SQLDONEINPROC: - { + { // RunBehavior can be modified - see SQL BU DT 269516 & 290090 result = TryProcessDone(cmdHandler, dataStream, ref runBehavior, stateObj); if (result != TdsOperationStatus.Done) @@ -2267,7 +2234,7 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle while (env != null) { - if (!this.Connection.IgnoreEnvChange) + if (!Connection.IgnoreEnvChange) { switch (env._type) { @@ -2405,7 +2372,8 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle if (tokenLength != TdsEnums.VARNULL) { _SqlMetaDataSet metadata; - result = TryProcessMetaData(tokenLength, stateObj, out metadata, cmdHandler?.ColumnEncryptionSetting ?? SqlCommandColumnEncryptionSetting.UseConnectionSetting); + result = TryProcessMetaData(tokenLength, stateObj, out metadata, + cmdHandler?.ColumnEncryptionSetting ?? SqlCommandColumnEncryptionSetting.UseConnectionSetting); if (result != TdsOperationStatus.Done) { return result; @@ -2539,7 +2507,8 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle case TdsEnums.SQLRETURNVALUE: { SqlReturnValue returnValue; - result = TryProcessReturnValue(tokenLength, stateObj, out returnValue, cmdHandler?.ColumnEncryptionSetting ?? SqlCommandColumnEncryptionSetting.UseConnectionSetting); + result = TryProcessReturnValue(tokenLength, stateObj, out returnValue, + cmdHandler?.ColumnEncryptionSetting ?? SqlCommandColumnEncryptionSetting.UseConnectionSetting); if (result != TdsOperationStatus.Done) { return result; @@ -2774,30 +2743,7 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb } break; - case TdsEnums.ENV_CHARSET: - // we copied this behavior directly from luxor - see charset envchange - // section from sqlctokn.c - result = TryReadTwoStringFields(env, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (env._newValue == TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_STRING) - { - _defaultCodePage = TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_VALUE; - _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); - } - else - { - Debug.Assert(env._newValue.Length > TdsEnums.CHARSET_CODE_PAGE_OFFSET, "TdsParser.ProcessEnvChange(): charset value received with length <=10"); - - string stringCodePage = env._newValue.Substring(TdsEnums.CHARSET_CODE_PAGE_OFFSET); - - _defaultCodePage = int.Parse(stringCodePage, NumberStyles.Integer, CultureInfo.InvariantCulture); - _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); - } - - break; + // TdsEnums.ENV_CHARSET (3) is only supported in TDS <= 7 which is no longer supported case TdsEnums.ENV_PACKETSIZE: // take care of packet size right here @@ -2824,29 +2770,15 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb // Update SNI ConsumerInfo value to be resulting packet size uint unsignedPacketSize = (uint)packetSize; uint bufferSizeResult = _physicalStateObj.SetConnectionBufferSize(ref unsignedPacketSize); + Debug.Assert(bufferSizeResult == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SNISetInfo"); } break; - case TdsEnums.ENV_LOCALEID: - // UNDONE: this LCID may be incorrect for OEM code pages on 7.0 - // need a way to get lcid from code page - result = TryReadTwoStringFields(env, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - _defaultLCID = int.Parse(env._newValue, NumberStyles.Integer, CultureInfo.InvariantCulture); - break; + // TdsEnums.ENV_LOCALE (5) is only supported in TDS <= 7 which is no longer supported - case TdsEnums.ENV_COMPFLAGS: - result = TryReadTwoStringFields(env, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; + // TdsEnums.ENV_COMPFLAGS (6) is only supported in TDS <= 7 which is no longer supported case TdsEnums.ENV_COLLATION: Debug.Assert(env._newLength == 5 || env._newLength == 0, "Improper length in new collation!"); @@ -2870,7 +2802,7 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb // UTF8 collation if (env._newCollation.IsUTF8) { - _defaultEncoding = Encoding.UTF8; + _defaultEncoding = s_utf8EncodingWithoutBom; } else { @@ -3856,7 +3788,6 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out { throw SQL.InvalidTDSVersion(); } - _is2005 = true; break; case TdsEnums.SQL2008_MAJOR << 24 | TdsEnums.SQL2008_MINOR: if (increment != TdsEnums.SQL2008_INCREMENT) @@ -3884,7 +3815,6 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out } _is2012 |= _is2022; _is2008 |= _is2012; - _is2005 |= _is2008; stateObj._outBytesUsed = stateObj._outputHeaderLen; byte len; @@ -4168,7 +4098,10 @@ internal TdsOperationStatus TryProcessError(byte token, TdsParserStateObject sta return TdsOperationStatus.Done; } - internal TdsOperationStatus TryProcessReturnValue(int length, TdsParserStateObject stateObj, out SqlReturnValue returnValue, SqlCommandColumnEncryptionSetting columnEncryptionSetting) + internal TdsOperationStatus TryProcessReturnValue(int length, + TdsParserStateObject stateObj, + out SqlReturnValue returnValue, + SqlCommandColumnEncryptionSetting columnEncryptionSetting) { returnValue = null; SqlReturnValue rec = new SqlReturnValue(); @@ -4307,6 +4240,15 @@ internal TdsOperationStatus TryProcessReturnValue(int length, TdsParserStateObje } } + if (tdsType == TdsEnums.SQLVECTOR) + { + result = stateObj.TryReadByte(out rec.scale); + if (result != TdsOperationStatus.Done) + { + return result; + } + } + if (rec.type == SqlDbType.Xml) { // Read schema info @@ -4368,7 +4310,7 @@ internal TdsOperationStatus TryProcessReturnValue(int length, TdsParserStateObje } } } - else if (rec.metaType.IsCharType) + else if (rec.metaType.IsCharType && rec.metaType.SqlDbType != SqlDbTypeExtensions.Json) { // read the collation for 8.x servers result = TryProcessCollation(stateObj, out rec.collation); @@ -4376,10 +4318,10 @@ internal TdsOperationStatus TryProcessReturnValue(int length, TdsParserStateObje { return result; } - + if (rec.collation.IsUTF8) { // UTF8 collation - rec.encoding = Encoding.UTF8; + rec.encoding = s_utf8EncodingWithoutBom; } else { @@ -4428,6 +4370,11 @@ internal TdsOperationStatus TryProcessReturnValue(int length, TdsParserStateObje intlen = int.MaxValue; // If plp data, read it all } + if (rec.type == SqlDbTypeExtensions.Vector) + { + rec.length = tdsLen; + } + if (isNull) { GetNullSqlValue(rec.value, rec, SqlCommandColumnEncryptionSetting.Disabled, _connHandler); @@ -4696,63 +4643,84 @@ internal int GetCodePage(SqlCollation collation, TdsParserStateObject stateObj) internal void DrainData(TdsParserStateObject stateObj) { +#if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); +#endif try { - SqlDataReader.SharedState sharedState = stateObj._readerState; - if (sharedState != null && sharedState._dataReady) + try { - _SqlMetaDataSet metadata = stateObj._cleanupMetaData; - if (stateObj._partialHeaderBytesRead > 0) + SqlDataReader.SharedState sharedState = stateObj._readerState; + if (sharedState != null && sharedState._dataReady) { - if (stateObj.TryProcessHeader() != TdsOperationStatus.Done) + _SqlMetaDataSet metadata = stateObj._cleanupMetaData; + if (stateObj._partialHeaderBytesRead > 0) { - throw SQL.SynchronousCallMayNotPend(); + if (stateObj.TryProcessHeader() != TdsOperationStatus.Done) + { + throw SQL.SynchronousCallMayNotPend(); + } } - } - if (0 == sharedState._nextColumnHeaderToRead) - { - // i. user called read but didn't fetch anything - if (stateObj.Parser.TrySkipRow(stateObj._cleanupMetaData, stateObj) != TdsOperationStatus.Done) + if (0 == sharedState._nextColumnHeaderToRead) { - throw SQL.SynchronousCallMayNotPend(); + // i. user called read but didn't fetch anything + if (stateObj.Parser.TrySkipRow(stateObj._cleanupMetaData, stateObj) != TdsOperationStatus.Done) + { + throw SQL.SynchronousCallMayNotPend(); + } } - } - else - { - // iia. if we still have bytes left from a partially read column, skip - if (sharedState._nextColumnDataToRead < sharedState._nextColumnHeaderToRead) + else { - if ((sharedState._nextColumnHeaderToRead > 0) && (metadata[sharedState._nextColumnHeaderToRead - 1].metaType.IsPlp)) + // iia. if we still have bytes left from a partially read column, skip + if (sharedState._nextColumnDataToRead < sharedState._nextColumnHeaderToRead) { - if (stateObj._longlen != 0) + if ((sharedState._nextColumnHeaderToRead > 0) && (metadata[sharedState._nextColumnHeaderToRead - 1].metaType.IsPlp)) { - if (TrySkipPlpValue(ulong.MaxValue, stateObj, out _) != TdsOperationStatus.Done) + if (stateObj._longlen != 0) { - throw SQL.SynchronousCallMayNotPend(); + if (TrySkipPlpValue(ulong.MaxValue, stateObj, out _) != TdsOperationStatus.Done) + { + throw SQL.SynchronousCallMayNotPend(); + } } } - } - else if (0 < sharedState._columnDataBytesRemaining) - { - if (stateObj.TrySkipLongBytes(sharedState._columnDataBytesRemaining) != TdsOperationStatus.Done) + else if (0 < sharedState._columnDataBytesRemaining) { - throw SQL.SynchronousCallMayNotPend(); + if (stateObj.TrySkipLongBytes(sharedState._columnDataBytesRemaining) != TdsOperationStatus.Done) + { + throw SQL.SynchronousCallMayNotPend(); + } } } - } - // Read the remaining values off the wire for this row - if (stateObj.Parser.TrySkipRow(metadata, sharedState._nextColumnHeaderToRead, stateObj) != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); + // Read the remaining values off the wire for this row + if (stateObj.Parser.TrySkipRow(metadata, sharedState._nextColumnHeaderToRead, stateObj) != TdsOperationStatus.Done) + { + throw SQL.SynchronousCallMayNotPend(); + } } } + Run(RunBehavior.Clean, null, null, null, stateObj); + } + catch + { + _connHandler.DoomThisConnection(); + throw; } - Run(RunBehavior.Clean, null, null, null, stateObj); } - catch + catch (OutOfMemoryException) + { + _connHandler.DoomThisConnection(); + throw; + } + catch (StackOverflowException) + { + _connHandler.DoomThisConnection(); + throw; + } + catch (ThreadAbortException) { _connHandler.DoomThisConnection(); throw; @@ -4810,13 +4778,13 @@ internal TdsOperationStatus TryProcessAltMetaData(int cColumns, TdsParserStateOb { // internal meta data class _SqlMetaData col = altMetaDataSet[i]; - + result = stateObj.TryReadByte(out _); if (result != TdsOperationStatus.Done) { return result; } - + result = stateObj.TryReadUInt16(out _); if (result != TdsOperationStatus.Done) { @@ -5205,7 +5173,7 @@ private TdsOperationStatus TryProcessTypeInfo(TdsParserStateObject stateObj, Sql } // read the collation for 7.x servers - if (col.metaType.IsCharType && (tdsType != TdsEnums.SQLXMLTYPE) && ((tdsType != TdsEnums.SQLJSON))) + if (col.metaType.IsCharType && (tdsType != TdsEnums.SQLXMLTYPE) && (tdsType != TdsEnums.SQLJSON)) { result = TryProcessCollation(stateObj, out col.collation); if (result != TdsOperationStatus.Done) @@ -5215,7 +5183,7 @@ private TdsOperationStatus TryProcessTypeInfo(TdsParserStateObject stateObj, Sql if (col.collation.IsUTF8) { // UTF8 collation - col.encoding = Encoding.UTF8; + col.encoding = s_utf8EncodingWithoutBom; } else { @@ -5234,6 +5202,15 @@ private TdsOperationStatus TryProcessTypeInfo(TdsParserStateObject stateObj, Sql } } + if (col.type == SqlDbTypeExtensions.Vector) + { + result = stateObj.TryReadByte(out col.scale); + if (result != TdsOperationStatus.Done) + { + return result; + } + } + return TdsOperationStatus.Done; } @@ -5325,7 +5302,8 @@ private TdsOperationStatus TryCommonProcessMetaData(TdsParserStateObject stateOb return TdsOperationStatus.Done; } - private void WriteUDTMetaData(object value, string database, string schema, string type, TdsParserStateObject stateObj) + private void WriteUDTMetaData(object value, string database, string schema, string type, + TdsParserStateObject stateObj) { // database if (string.IsNullOrEmpty(database)) @@ -5499,7 +5477,7 @@ private TdsOperationStatus TryProcessColInfo(_SqlMetaDataSet columns, SqlDataRea for (int i = 0; i < columns.Length; i++) { _SqlMetaData col = columns[i]; - + TdsOperationStatus result = stateObj.TryReadByte(out _); if (result != TdsOperationStatus.Done) { @@ -5706,7 +5684,9 @@ private TdsOperationStatus TryProcessRow(_SqlMetaDataSet columns, object[] buffe // We only read up to 2Gb. Throw if data is larger. Very large data // should be read in chunks in sequential read mode // For Plp columns, we may have gotten only the length of the first chunk - result = TryReadSqlValue(data, md, md.metaType.IsPlp ? (Int32.MaxValue) : (int)len, stateObj, SqlCommandColumnEncryptionSetting.Disabled /*Column Encryption Disabled for Bulk Copy*/, md.column); + result = TryReadSqlValue(data, md, md.metaType.IsPlp ? (int.MaxValue) : (int)len, stateObj, + SqlCommandColumnEncryptionSetting.Disabled /*Column Encryption Disabled for Bulk Copy*/, + md.column); if (result != TdsOperationStatus.Done) { return result; @@ -5742,11 +5722,15 @@ internal static bool ShouldHonorTceForRead(SqlCommandColumnEncryptionSetting col // Check connection level setting! Debug.Assert(SqlCommandColumnEncryptionSetting.UseConnectionSetting == columnEncryptionSetting, "Unexpected value for command level override"); - return (connection != null && connection.ConnectionOptions != null && connection.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled); + return (connection != null && connection.ConnectionOptions != null + && connection.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled); } } - internal static object GetNullSqlValue(SqlBuffer nullVal, SqlMetaDataPriv md, SqlCommandColumnEncryptionSetting columnEncryptionSetting, SqlInternalConnectionTds connection) + internal static object GetNullSqlValue(SqlBuffer nullVal, + SqlMetaDataPriv md, + SqlCommandColumnEncryptionSetting columnEncryptionSetting, + SqlInternalConnectionTds connection) { SqlDbType type = md.type; @@ -5857,6 +5841,11 @@ internal static object GetNullSqlValue(SqlBuffer nullVal, SqlMetaDataPriv md, Sq nullVal.SetToNullOfType(SqlBuffer.StorageType.Json); break; + case SqlDbTypeExtensions.Vector: + nullVal.SetToNullOfType(SqlBuffer.StorageType.Vector); + nullVal.SetVectorInfo(MetaType.GetVectorElementCount(md.length, md.scale), md.scale, true); + break; + default: Debug.Fail("unknown null sqlType!" + md.type.ToString()); break; @@ -6007,7 +5996,7 @@ private TdsOperationStatus TryReadSqlStringValue(SqlBuffer value, byte type, int break; case TdsEnums.SQLJSON: - encoding = Encoding.UTF8; + encoding = s_utf8EncodingWithoutBom; string jsonStringValue; result = stateObj.TryReadStringWithEncoding(length, encoding, isPlp, out jsonStringValue); if (result != TdsOperationStatus.Done) @@ -6037,7 +6026,7 @@ private TdsOperationStatus TryReadSqlStringValue(SqlBuffer value, byte type, int } else { - s = ""; + s = string.Empty; } } @@ -6359,7 +6348,13 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt return true; } - internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, SqlMetaDataPriv md, int length, TdsParserStateObject stateObj, SqlCommandColumnEncryptionSetting columnEncryptionOverride, string columnName, SqlCommand command = null) + internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, + SqlMetaDataPriv md, + int length, + TdsParserStateObject stateObj, + SqlCommandColumnEncryptionSetting columnEncryptionOverride, + string columnName, + SqlCommand command = null) { bool isPlp = md.metaType.IsPlp; byte tdsType = md.tdsType; @@ -6472,10 +6467,32 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, SqlMetaDataPriv md, } else { - value.SqlBinary = SqlBinary.WrapBytes(b); + value.SqlBinary = SqlBinary.WrapBytes(b); // doesn't copy the byte array } break; + case TdsEnums.SQLVECTOR: + // Vector data is read as non-plp binary value. + // This is same as reading varbinary(8000). + byte[] buff = new byte[length]; + result = stateObj.TryReadByteArray(buff, length); + if (result != TdsOperationStatus.Done) + { + return result; + } + + // Internally, we use Sqlbinary to deal with varbinary data and store it in + // SqlBuffer as SqlBinary value. + value.SqlBinary = SqlBinary.WrapBytes(buff); + + // Extract the metadata from the payload and set it as the vector attributes + // in the SqlBuffer. This metadata is further used when constructing a SqlVector + // object from binary payload. + int elementCount = BinaryPrimitives.ReadUInt16LittleEndian(buff.AsSpan(2)); + byte elementType = buff[4]; + value.SetVectorInfo(elementCount, elementType, false); + break; + case TdsEnums.SQLCHAR: case TdsEnums.SQLBIGCHAR: case TdsEnums.SQLVARCHAR: @@ -6764,8 +6781,9 @@ internal TdsOperationStatus TryReadSqlValueInternal(SqlBuffer value, byte tdsTyp case TdsEnums.SQLUNIQUEID: { - Debug.Assert(length == 16, "invalid length for SqlGuid type!"); - Span b = stackalloc byte[16]; + Debug.Assert(length == GUID_SIZE, "invalid length for SqlGuid type!"); + + Span b = stackalloc byte[GUID_SIZE]; result = stateObj.TryReadByteArray(b, length); if (result != TdsOperationStatus.Done) { @@ -6780,6 +6798,7 @@ internal TdsOperationStatus TryReadSqlValueInternal(SqlBuffer value, byte tdsTyp case TdsEnums.SQLBIGVARBINARY: case TdsEnums.SQLVARBINARY: case TdsEnums.SQLIMAGE: + case TdsEnums.SQLVECTOR: { // Note: Better not come here with plp data!! Debug.Assert(length <= TdsEnums.MAXSIZE); @@ -6789,7 +6808,7 @@ internal TdsOperationStatus TryReadSqlValueInternal(SqlBuffer value, byte tdsTyp { return result; } - value.SqlBinary = SqlBinary.WrapBytes(b); + value.SqlBinary = SqlBinary.WrapBytes(b); // doesn't copy the byte array break; } @@ -7377,7 +7396,7 @@ private byte[] SerializeSqlMoney(SqlMoney value, int length, TdsParserStateObjec private void WriteSqlMoney(SqlMoney value, int length, TdsParserStateObject stateObj) { - // UNDONE: can I use SqlMoney.ToInt64()? + // UNDONE: can I use SqlMoney.ToInt64()? int[] bits = decimal.GetBits(value.Value); // this decimal should be scaled by 10000 (regardless of what the incoming decimal was scaled by) @@ -8050,7 +8069,6 @@ internal TdsOperationStatus TryGetTokenLength(byte token, TdsParserStateObject s tokenLength = -1; return TdsOperationStatus.Done; case TdsEnums.SQLSESSIONSTATE: - return stateObj.TryReadInt32(out tokenLength); case TdsEnums.SQLFEDAUTHINFO: return stateObj.TryReadInt32(out tokenLength); } @@ -8084,6 +8102,18 @@ internal TdsOperationStatus TryGetTokenLength(byte token, TdsParserStateObject s tokenLength = -1; return TdsOperationStatus.Done; } + else if (token == TdsEnums.SQLVECTOR) + { + ushort value; + result = stateObj.TryReadUInt16(out value); + if (result != TdsOperationStatus.Done) + { + tokenLength = 0; + return result; + } + tokenLength = value; + return TdsOperationStatus.Done; + } } switch (token & TdsEnums.SQLLenMask) @@ -8407,7 +8437,7 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD internal int WriteTceFeatureRequest(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 = Version if (write) { @@ -8513,6 +8543,36 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD return len; } + /// + /// Writes the Vector Support feature request to the physical state object. + /// The request includes the feature ID, feature data length, and version number. + /// + /// If true, writes the feature request to the physical state object. + /// The length of the feature request in bytes. + /// + /// The feature request consists of: + /// - 1 byte for the feature ID. + /// - 4 bytes for the feature data length. + /// - 1 byte for the version number. + /// + internal int WriteVectorSupportFeatureRequest(bool write) + { + const int len = 6; + + if (write) + { + // Write Feature ID + _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_VECTORSUPPORT); + + // Feature Data Length + WriteInt(1, _physicalStateObj); + + _physicalStateObj.WriteByte(TdsEnums.MAX_SUPPORTED_VECTOR_VERSION); + } + + return len; + } + private void WriteLoginData(SqlLogin rec, TdsEnums.FeatureExtension requestedFeatures, SessionData recoverySessionData, @@ -8527,8 +8587,7 @@ private void WriteLoginData(SqlLogin rec, int length, int featureExOffset, string clientInterfaceName, - byte[] outSSPIBuff, - uint outSSPILength) + ReadOnlySpan outSSPI) { try { @@ -8700,8 +8759,8 @@ private void WriteLoginData(SqlLogin rec, WriteShort(offset, _physicalStateObj); // ibSSPI offset if (rec.useSSPI) { - WriteShort((int)outSSPILength, _physicalStateObj); - offset += (int)outSSPILength; + WriteShort(outSSPI.Length, _physicalStateObj); + offset += outSSPI.Length; } else { @@ -8720,7 +8779,7 @@ private void WriteLoginData(SqlLogin rec, // write variable length portion WriteString(rec.hostName, _physicalStateObj); - // if we are using SSPI, do not send over username/password, since we will use SSPI instead + // if we are using SSPI or fed auth MSAL, do not send over username/password, since we will use SSPI instead // same behavior as Luxor if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) { @@ -8756,7 +8815,7 @@ private void WriteLoginData(SqlLogin rec, // send over SSPI data if we are using SSPI if (rec.useSSPI) - _physicalStateObj.WriteByteArray(outSSPIBuff, (int)outSSPILength, 0); + _physicalStateObj.WriteByteSpan(outSSPI); WriteString(rec.attachDBFilename, _physicalStateObj); if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) @@ -8837,6 +8896,11 @@ private int ApplyFeatureExData(TdsEnums.FeatureExtension requestedFeatures, length += WriteJsonSupportFeatureRequest(write); } + if ((requestedFeatures & TdsEnums.FeatureExtension.VectorSupport) != 0) + { + length += WriteVectorSupportFeatureRequest(write); + } + length++; // for terminator if (write) { @@ -9429,7 +9493,8 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout if ( !(cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled || - (cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && cmd.Connection.IsColumnEncryptionSettingEnabled))) + (cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && cmd.Connection.IsColumnEncryptionSettingEnabled)) + ) { throw SQL.ParamInvalidForceColumnEncryptionSetting(param.ParameterName, rpcext.GetCommandTextOrRpcName()); } @@ -9459,8 +9524,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout continue; } - if ((!_is2005 && !mt.Is80Supported) || - (!_is2008 && !mt.Is90Supported)) + if (!_is2008 && !mt.Is90Supported) { throw ADP.VersionDoesNotSupportDataType(mt.TypeName); } @@ -9593,7 +9657,13 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet if (param.Direction == ParameterDirection.Output) { isSqlVal = param.ParameterIsSqlType; // We have to forward the TYPE info, we need to know what type we are returning. Once we null the parameter we will no longer be able to distinguish what type were seeing. - param.Value = null; + + // Output parameter of SqlDbType vector are defined through SqlParameter.Value. + // This check ensures that we do not discard the parameter value when SqlDbType is vector. + if (mt.SqlDbType != SqlDbTypeExtensions.Vector) + { + param.Value = null; + } param.ParameterIsSqlType = isSqlVal; } else @@ -9825,11 +9895,9 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet } else if (mt.SqlDbType == SqlDbType.Udt) { - Debug.Assert(_is2005, "Invalid DataType UDT for non-2005 or later server!"); - int maxSupportedSize = Is2008OrNewer ? int.MaxValue : short.MaxValue; byte[] udtVal = null; - SqlServer.Server.Format format = SqlServer.Server.Format.Native; + Format format = Format.Native; if (string.IsNullOrEmpty(param.UdtTypeName)) { @@ -9893,7 +9961,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet WriteUDTMetaData(value, names[0], names[1], names[2], stateObj); - // UNDONE - re-org to use code below to write value! + // UNDONE - re-org to use code below to write value! if (!isNull) { WriteUnsignedLong((ulong)udtVal.Length, stateObj); // PLP length @@ -9919,7 +9987,7 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet else if ((!mt.IsVarTime) && (mt.SqlDbType != SqlDbType.Date)) { // Time, Date, DateTime2, DateTimeoffset do not have the size written out maxsize = (size > actualSize) ? size : actualSize; - if (maxsize == 0 && _is2005) + if (maxsize == 0) { // 2005 doesn't like 0 as MaxSize. Change it to 2 for unicode types (SQL9 - 682322) if (mt.IsNCharType) @@ -9928,6 +9996,14 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet maxsize = 1; } + if (mt.SqlDbType == SqlDbTypeExtensions.Vector) + { + // For vector type we need to write the size in bytes required to represent + // vector value when communicating with SQL Server. + var sqlVectorProps = ((ISqlVector)param.Value); + maxsize = sqlVectorProps.Size; + } + WriteParameterVarLen(mt, maxsize, false /*IsNull*/, stateObj); } } @@ -9950,7 +10026,11 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet { stateObj.WriteByte(param.GetActualScale()); } - + else if (mt.SqlDbType == SqlDbTypeExtensions.Vector) + { + // For vector type we need to write scale as the element type of the vector. + stateObj.WriteByte(((ISqlVector)param.Value).ElementType); + } // write out collation or xml metadata if ((mt.SqlDbType == SqlDbType.Xml || mt.SqlDbType == SqlDbTypeExtensions.Json)) @@ -10029,7 +10109,9 @@ private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParamet { // for codePageEncoded types, WriteValue simply expects the number of characters // For plp types, we also need the encoded byte size - writeParamTask = WriteValue(value, mt, isParameterEncrypted ? (byte)0 : param.GetActualScale(), actualSize, codePageByteSize, isParameterEncrypted ? 0 : param.Offset, stateObj, isParameterEncrypted ? 0 : param.Size, isDataFeed); + // For vector type we need to write scale as the element type of the vector. + byte writeScale = mt.SqlDbType == SqlDbTypeExtensions.Vector ? ((ISqlVector)param.Value).ElementType : param.GetActualScale(); + writeParamTask = WriteValue(value, mt, isParameterEncrypted ? (byte)0 : writeScale, actualSize, codePageByteSize, isParameterEncrypted ? 0 : param.Offset, stateObj, isParameterEncrypted ? 0 : param.Size, isDataFeed); } } @@ -10098,7 +10180,28 @@ private void FinalizeExecuteRPC(TdsParserStateObject stateObj) private void TdsExecuteRPC_OnFailure(Exception exc, TdsParserStateObject stateObj) { - FailureCleanup(stateObj, exc); +#if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); +#endif + try + { + FailureCleanup(stateObj, exc); + } + catch (OutOfMemoryException) + { + _connHandler.DoomThisConnection(); + throw; + } + catch (StackOverflowException) + { + _connHandler.DoomThisConnection(); + throw; + } + catch (ThreadAbortException) + { + _connHandler.DoomThisConnection(); + throw; + } } private void ExecuteFlushTaskCallback(Task tsk, TdsParserStateObject stateObj, TaskCompletionSource completion, bool releaseConnectionLock) @@ -10109,10 +10212,32 @@ private void ExecuteFlushTaskCallback(Task tsk, TdsParserStateObject stateObj, T if (tsk.Exception != null) { Exception exc = tsk.Exception.InnerException; + +#if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); +#endif try { FailureCleanup(stateObj, tsk.Exception); } + catch (OutOfMemoryException e) + { + _connHandler.DoomThisConnection(); + completion.SetException(e); + throw; + } + catch (StackOverflowException e) + { + _connHandler.DoomThisConnection(); + completion.SetException(e); + throw; + } + catch (ThreadAbortException e) + { + _connHandler.DoomThisConnection(); + completion.SetException(e); + throw; + } catch (Exception e) { exc = e; @@ -10215,7 +10340,8 @@ private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefa else { value = param.GetCoercedValue(); - typeCode = MetaDataUtilsSmi.DetermineExtendedTypeCodeForUseWithSqlDbType(metaData.SqlDbType, metaData.IsMultiValued, value, null); + typeCode = MetaDataUtilsSmi.DetermineExtendedTypeCodeForUseWithSqlDbType( + metaData.SqlDbType, metaData.IsMultiValued, value, null); } if (advancedTraceIsOn) @@ -10234,7 +10360,6 @@ private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefa // TdsParameterSetter paramSetter = new TdsParameterSetter(stateObj, metaData); ValueUtilsSmi.SetCompatibleValueV200( - new SmiEventSink_Default(), // TDS Errors/events dealt with at lower level for now, just need an object for processing paramSetter, 0, // ordinal. TdsParameterSetter only handles one parameter at a time metaData, @@ -10818,6 +10943,11 @@ internal void WriteBulkCopyMetaData(_SqlMetaDataSet metadataCollection, int coun case SqlDbTypeExtensions.Json: stateObj.WriteByteArray(s_jsonMetadataSubstituteSequence, s_jsonMetadataSubstituteSequence.Length, 0); break; + case SqlDbTypeExtensions.Vector: + stateObj.WriteByte(md.tdsType); + WriteTokenLength(md.tdsType, md.length, stateObj); + stateObj.WriteByte(md.scale); + break; default: stateObj.WriteByte(md.tdsType); WriteTokenLength(md.tdsType, md.length, stateObj); @@ -10995,7 +11125,7 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars // Replace encoding if it is UTF8 if (metadata.collation.IsUTF8) { - _defaultEncoding = Encoding.UTF8; + _defaultEncoding = s_utf8EncodingWithoutBom; } _defaultCollation = metadata.collation; @@ -11033,6 +11163,7 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars case TdsEnums.SQLBIGVARBINARY: case TdsEnums.SQLIMAGE: case TdsEnums.SQLUDT: + case TdsEnums.SQLVECTOR: ccb = (isSqlType) ? ((SqlBinary)value).Length : ((byte[])value).Length; break; case TdsEnums.SQLUNIQUEID: @@ -11181,7 +11312,6 @@ private Task WriteBulkCopyValueSetupContinuation(Task internalWriteTask, Encodin // Write mars header data, not including the mars header length private void WriteMarsHeaderData(TdsParserStateObject stateObj, SqlInternalTransaction transaction) { - // Function to send over additional payload header data for 2005 and beyond only. // We may need to update the mars header length if mars header is changed in the future WriteShort(TdsEnums.HEADERTYPE_MARS, stateObj); @@ -11252,8 +11382,6 @@ private int GetNotificationHeaderSize(SqlNotificationRequest notificationRequest // Write query notificaiton header data, not including the notificaiton header length private void WriteQueryNotificationHeaderData(SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj) { - Debug.Assert(_is2005, "WriteQueryNotificationHeaderData called on a non-2005 server"); - // We may need to update the notification header length if the header is changed in the future Debug.Assert(notificationRequest != null, "notificationRequest is null"); @@ -11312,25 +11440,39 @@ private void WriteRPCBatchHeaders(TdsParserStateObject stateObj, SqlNotification int notificationHeaderSize = GetNotificationHeaderSize(notificationRequest); - const int marsHeaderSize = 18; // 4 + 2 + 8 + 4 + const int MarsHeaderSize = 18; // 4 + 2 + 8 + 4 + + // Header Length (DWORD) + // Header Type (ushort) + // Trace Data Guid + // Trace Data Sequence Number (uint) + const int TraceHeaderSize = 26; // 4 + 2 + sizeof(Guid) + sizeof(uint); - int totalHeaderLength = 4 + marsHeaderSize + notificationHeaderSize; + // TotalLength - DWORD - including all headers and lengths, including itself + int totalHeaderLength = IncludeTraceHeader ? (4 + MarsHeaderSize + notificationHeaderSize + TraceHeaderSize) : (4 + MarsHeaderSize + notificationHeaderSize); Debug.Assert(stateObj._outBytesUsed == stateObj._outputHeaderLen, "Output bytes written before total header length"); // Write total header length WriteInt(totalHeaderLength, stateObj); - // Write Mars header length - WriteInt(marsHeaderSize, stateObj); - // Write Mars header data + // Write MARS header length and data + WriteInt(MarsHeaderSize, stateObj); WriteMarsHeaderData(stateObj, CurrentTransaction); - if (0 != notificationHeaderSize) + if (notificationHeaderSize != 0) { - // Write Notification header length + // Write notification header length and data + // MS-TDS 2.2.5.3.1: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/e168d373-a7b7-41aa-b6ca-25985466a7e0 WriteInt(notificationHeaderSize, stateObj); - // Write notificaiton header data WriteQueryNotificationHeaderData(notificationRequest, stateObj); } + + if (IncludeTraceHeader) + { + // Write trace header length and data + // MS-TDS 2.2.5.3.3: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/6e9f106b-df6e-4cbe-a6eb-45ceb10c63be + WriteInt(TraceHeaderSize, stateObj); + WriteTraceHeaderData(stateObj); + } } @@ -11355,6 +11497,12 @@ private void WriteTokenLength(byte token, int length, TdsParserStateObject state { tokenLength = 8; } + else if (token == TdsEnums.SQLVECTOR) + { + tokenLength = 2; + WriteShort(length, stateObj); + return; + } } if (tokenLength == 0) @@ -11455,7 +11603,9 @@ private Task GetTerminationTask(Task unterminatedWriteTask, object value, MetaTy } else { - return AsyncHelper.CreateContinuationTask(unterminatedWriteTask, WriteInt, 0, stateObj); + return AsyncHelper.CreateContinuationTask(unterminatedWriteTask, + WriteInt, 0, stateObj + ); } } else @@ -11775,14 +11925,35 @@ public override Task WriteAsync(byte[] buffer, int offset, int count, Cancellati StripPreamble(buffer, ref offset, ref count); - Task task = null; - if (count > 0) +#if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); +#endif + try { - _parser.WriteInt(count, _stateObj); // write length of chunk - task = _stateObj.WriteByteArray(buffer, count, offset, canAccumulate: false); - } + Task task = null; + if (count > 0) + { + _parser.WriteInt(count, _stateObj); // write length of chunk + task = _stateObj.WriteByteArray(buffer, count, offset, canAccumulate: false); + } - return task ?? Task.CompletedTask; + return task ?? Task.CompletedTask; + } + catch (OutOfMemoryException) + { + _parser._connHandler.DoomThisConnection(); + throw; + } + catch (StackOverflowException) + { + _parser._connHandler.DoomThisConnection(); + throw; + } + catch (ThreadAbortException) + { + _parser._connHandler.DoomThisConnection(); + throw; + } } internal static void ValidateWriteParameters(byte[] buffer, int offset, int count) @@ -11947,7 +12118,7 @@ private async Task WriteXmlFeed(XmlDataFeed feed, TdsParserStateObject stateObj, } XmlWriterSettings writerSettings = new XmlWriterSettings(); - writerSettings.CloseOutput = false; // don't close the memory stream + writerSettings.CloseOutput = false; // don't close the memory stream writerSettings.ConformanceLevel = ConformanceLevel.Fragment; if (_asyncWrite) { @@ -12168,6 +12339,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int case TdsEnums.SQLBIGVARBINARY: case TdsEnums.SQLIMAGE: case TdsEnums.SQLUDT: + case TdsEnums.SQLVECTOR: { // An array should be in the object Debug.Assert(isDataFeed || value is byte[], "Value should be an array of bytes"); @@ -12263,7 +12435,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: case TdsEnums.SQLXMLTYPE: - case TdsEnums.SQLJSON: + case TdsEnums.SQLJSON: { Debug.Assert(!isDataFeed || (value is TextDataFeed || value is XmlDataFeed), "Value must be a TextReader or XmlReader"); Debug.Assert(isDataFeed || (value is string || value is byte[]), "Value is a byte array or string"); @@ -12539,7 +12711,9 @@ private byte[] SerializeUnencryptedValue(object value, MetaType type, byte scale if (type.FixedLength == 4) { if (0 > dt.days || dt.days > UInt16.MaxValue) + { throw SQL.SmallDateTimeOverflow(MetaType.ToDateTime(dt.days, dt.time, 4).ToString(CultureInfo.InvariantCulture)); + } if (stateObj._bIntBytes == null) { @@ -12902,7 +13076,14 @@ internal int ReadPlpUnicodeChars(ref char[] buff, int offst, int len, TdsParserS // requested length is -1 or larger than the actual length of data. First call to this method // should be preceeded by a call to ReadPlpLength or ReadDataLength. // Returns the actual chars read. - internal TdsOperationStatus TryReadPlpUnicodeChars(ref char[] buff, int offst, int len, TdsParserStateObject stateObj, out int totalCharsRead, bool supportRentedBuff, ref bool rentedBuff) + internal TdsOperationStatus TryReadPlpUnicodeChars( + ref char[] buff, + int offst, + int len, + TdsParserStateObject stateObj, + out int totalCharsRead, + bool supportRentedBuff, + ref bool rentedBuff) { int charsRead = 0; int charsLeft = 0; @@ -12915,14 +13096,14 @@ internal TdsOperationStatus TryReadPlpUnicodeChars(ref char[] buff, int offst, i return TdsOperationStatus.Done; // No data } - Debug.Assert(((ulong)stateObj._longlen != TdsEnums.SQL_PLP_NULL), "Out of sync plp read request"); + Debug.Assert((ulong)stateObj._longlen != TdsEnums.SQL_PLP_NULL, "Out of sync plp read request"); Debug.Assert((buff == null && offst == 0) || (buff.Length >= offst + len), "Invalid length sent to ReadPlpUnicodeChars()!"); charsLeft = len; - // If total length is known up front, the length isn't specified as unknown - // and the caller doesn't pass int.max/2 indicating that it doesn't know the length - // allocate the whole buffer in one shot instead of realloc'ing and copying over each time + // If total data length is known up front from the plp header by being not SQL_PLP_UNKNOWNLEN + // and the number of chars required is less than int.max/2 allocate the entire buffer now to avoid + // later needing to repeatedly allocate new target buffers and copy data as we discover new data if (buff == null && stateObj._longlen != TdsEnums.SQL_PLP_UNKNOWNLEN && len < (int.MaxValue >> 1)) { if (supportRentedBuff && len < 1073741824) // 1 Gib @@ -13303,16 +13484,14 @@ private TdsOperationStatus TryProcessUDTMetaData(SqlMetaDataPriv metaData, TdsPa + " _connHandler = {14}\n\t" + " _fMARS = {15}\n\t" + " _sessionPool = {16}\n\t" - + " _is2005 = {17}\n\t" - + " _sniSpnBuffer = {18}\n\t" - + " _errors = {19}\n\t" - + " _warnings = {20}\n\t" - + " _attentionErrors = {21}\n\t" - + " _attentionWarnings = {22}\n\t" - + " _statistics = {23}\n\t" - + " _statisticsIsInTransaction = {24}\n\t" - + " _fPreserveTransaction = {25}" - + " _fParallel = {26}" + + " _errors = {17}\n\t" + + " _warnings = {18}\n\t" + + " _attentionErrors = {19}\n\t" + + " _attentionWarnings = {20}\n\t" + + " _statistics = {21}\n\t" + + " _statisticsIsInTransaction = {22}\n\t" + + " _fPreserveTransaction = {23}" + + " _fParallel = {24}" ; internal string TraceString() { @@ -13335,8 +13514,6 @@ internal string TraceString() _connHandler == null ? "(null)" : _connHandler.ObjectID.ToString((IFormatProvider)null), _fMARS ? bool.TrueString : bool.FalseString, _sessionPool == null ? "(null)" : _sessionPool.TraceString(), - _is2005 ? bool.TrueString : bool.FalseString, - _sniSpnBuffer == null ? "(null)" : _sniSpnBuffer.Length.ToString((IFormatProvider)null), _physicalStateObj != null ? "(null)" : _physicalStateObj.ErrorCount.ToString((IFormatProvider)null), _physicalStateObj != null ? "(null)" : _physicalStateObj.WarningCount.ToString((IFormatProvider)null), _physicalStateObj != null ? "(null)" : _physicalStateObj.PreAttentionErrorCount.ToString((IFormatProvider)null), diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.netcore.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.NetCoreApp.cs rename to src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.netcore.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs index 4f655dc403..4ab44115e0 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObject.netcore.cs @@ -3,14 +3,15 @@ // See the LICENSE file in the project root for more information. using System; -using System.Diagnostics; using System.Buffers.Binary; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Security; using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ManagedSni; namespace Microsoft.Data.SqlClient { @@ -27,13 +28,6 @@ internal static void PrepareConstrainedRegions() } } - private static readonly ContextCallback s_readAsyncCallbackComplete = ReadAsyncCallbackComplete; - - // Timeout variables - private readonly WeakReference _cancellationOwner = new WeakReference(null); - - // Async - ////////////////// // Constructors // ////////////////// @@ -62,7 +56,7 @@ internal TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalCon AddError(parser.ProcessSNIError(this)); ThrowExceptionAndWarning(); } - + // we post a callback that represents the call to dispose; once the // object is disposed, the next callback will cause the GC Handle to // be released. @@ -70,123 +64,15 @@ internal TdsParserStateObject(TdsParser parser, TdsParserStateObject physicalCon _lastSuccessfulIOTimer = parser._physicalStateObj._lastSuccessfulIOTimer; } - //////////////// - // Properties // - //////////////// - internal abstract uint DisableSsl(); - - internal abstract SSPIContextProvider CreateSSPIContextProvider(); - - internal abstract uint EnableMars(ref uint info); - - internal abstract uint Status - { - get; - } - - internal abstract Guid? SessionId { get; } - - internal abstract SessionHandle SessionHandle - { - get; - } - ///////////////////// // General methods // ///////////////////// - // This method is only called by the command or datareader as a result of a user initiated - // cancel request. - internal void Cancel(object caller) - { - Debug.Assert(caller != null, "Null caller for Cancel!"); - Debug.Assert(caller is SqlCommand || caller is SqlDataReader, "Calling API with invalid caller type: " + caller.GetType()); - - bool hasLock = false; - try - { - // Keep looping until we either grabbed the lock (and therefore sent attention) or the connection closes\breaks - while ((!hasLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) - { - Monitor.TryEnter(this, WaitForCancellationLockPollTimeout, ref hasLock); - if (hasLock) - { // Lock for the time being - since we need to synchronize the attention send. - // This lock is also protecting against concurrent close and async continuations - - // Ensure that, once we have the lock, that we are still the owner - if ((!_cancelled) && (_cancellationOwner.Target == caller)) - { - _cancelled = true; - - if (HasPendingData && !_attentionSent) - { - bool hasParserLock = false; - // Keep looping until we have the parser lock (and so are allowed to write), or the connection closes\breaks - while ((!hasParserLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) - { - try - { - _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false, timeout: WaitForCancellationLockPollTimeout, lockTaken: ref hasParserLock); - if (hasParserLock) - { - _parser.Connection.ThreadHasParserLockForClose = true; - SendAttention(); - } - } - finally - { - if (hasParserLock) - { - if (_parser.Connection.ThreadHasParserLockForClose) - { - _parser.Connection.ThreadHasParserLockForClose = false; - } - _parser.Connection._parserLock.Release(); - } - } - } - } - } - } - } - } - finally - { - if (hasLock) - { - Monitor.Exit(this); - } - } - } - - private void ResetCancelAndProcessAttention() - { - // This method is shared by CloseSession initiated by DataReader.Close or completed - // command execution, as well as the session reclamation code for cases where the - // DataReader is opened and then GC'ed. - lock (this) - { - // Reset cancel state. - _cancelled = false; - _cancellationOwner.Target = null; - - if (_attentionSent) - { - // Make sure we're cleaning up the AttentionAck if Cancel happened before taking the lock. - // We serialize Cancel/CloseSession to prevent a race condition between these two states. - // The problem is that both sending and receiving attentions are time taking - // operations. - Parser.ProcessPendingAck(this); - } - SetTimeoutStateStopped(); - } - } - internal abstract void CreatePhysicalSNIHandle( string serverName, TimeoutTimer timeout, out byte[] instanceName, - ref byte[][] spnBuffer, + out ResolvedServerSpn resolvedSpn, bool flushCache, bool async, bool fParallel, @@ -201,10 +87,6 @@ internal abstract void CreatePhysicalSNIHandle( internal abstract void AssignPendingDNSInfo(string userProtocol, string DNSCacheKey, ref SQLDNSInfo pendingDNSInfo); - internal abstract uint SniGetConnectionId(ref Guid clientConnectionId); - - internal abstract bool IsFailedHandle(); - protected abstract void CreateSessionHandle(TdsParserStateObject physicalConnection, bool async); protected abstract void FreeGcHandle(int remaining, bool release); @@ -215,30 +97,8 @@ internal abstract void CreatePhysicalSNIHandle( internal abstract void Dispose(); - internal abstract void DisposePacketCache(); - - internal abstract bool IsPacketEmpty(PacketHandle readPacket); - - internal abstract PacketHandle ReadSyncOverAsync(int timeoutRemaining, out uint error); - - internal abstract PacketHandle ReadAsync(SessionHandle handle, out uint error); - internal abstract uint CheckConnection(); - internal abstract uint SetConnectionBufferSize(ref uint unsignedPacketSize); - - internal abstract void ReleasePacket(PacketHandle syncReadPacket); - - protected abstract uint SNIPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize); - - internal abstract PacketHandle GetResetWritePacket(int dataSize); - - internal abstract void ClearAllWritePackets(); - - internal abstract PacketHandle AddPacketToPendingList(PacketHandle packet); - - protected abstract void RemovePacketFromPendingList(PacketHandle pointer); - internal int DecrementPendingCallbacks(bool release) { int remaining = Interlocked.Decrement(ref _pendingCallbacks); @@ -250,19 +110,6 @@ internal int DecrementPendingCallbacks(bool release) return remaining; } - internal int IncrementPendingCallbacks() - { - int remaining = Interlocked.Increment(ref _pendingCallbacks); - SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObject.IncrementPendingCallbacks | ADV | State Object Id {0}, after incrementing _pendingCallbacks: {1}", _objectID, _pendingCallbacks); - Debug.Assert(0 < remaining && remaining <= 3, $"_pendingCallbacks values is invalid after incrementing: {remaining}"); - return remaining; - } - - internal void StartSession(object cancellationOwner) - { - _cancellationOwner.Target = cancellationOwner; - } - /// /// Checks to see if the underlying connection is still valid (used by idle connection resiliency - for active connections) /// NOTE: This is not safe to do on a connection that is currently in use @@ -298,8 +145,6 @@ internal bool ValidateSNIConnection() // This method should only be called by ReadSni! If not - it may have problems with timeouts! private void ReadSniError(TdsParserStateObject stateObj, uint error) { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadSniSyncError"); // you need to setup for a thread abort somewhere before you call this method - if (TdsEnums.SNI_WAIT_TIMEOUT == error) { Debug.Assert(_syncOverAsync, "Should never reach here with async on!"); @@ -355,6 +200,7 @@ private void ReadSniError(TdsParserStateObject stateObj, uint error) if (!IsPacketEmpty(syncReadPacket)) { + // Be sure to release packet, otherwise it will be leaked by native. ReleasePacket(syncReadPacket); } } @@ -411,7 +257,7 @@ public void ProcessSniPacket(PacketHandle packet, uint error) { uint dataSize = 0; - uint getDataError = SNIPacketGetData(packet, _inBuff, ref dataSize); + uint getDataError = SniPacketGetData(packet, _inBuff, ref dataSize); if (getDataError == TdsEnums.SNI_SUCCESS) { @@ -449,23 +295,6 @@ public void ProcessSniPacket(PacketHandle packet, uint error) } } - private void ChangeNetworkPacketTimeout(int dueTime, int period) - { - Timer networkPacketTimeout = _networkPacketTimeout; - if (networkPacketTimeout != null) - { - try - { - networkPacketTimeout.Change(dueTime, period); - } - catch (ObjectDisposedException) - { - // _networkPacketTimeout is set to null before Disposing, but there is still a slight chance - // that object was disposed after we took a copy - } - } - } - private void SetBufferSecureStrings() { if (_securePasswords != null) @@ -547,7 +376,7 @@ public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) // The timer thread may be unreliable under high contention scenarios. It cannot be // assumed that the timeout has happened on the timer thread callback. Check the timeout - // synchrnously and then call OnTimeoutSync to force an atomic change of state. + // synchronously and then call OnTimeoutSync to force an atomic change of state. if (TimeoutHasExpired) { OnTimeoutSync(asyncClose: true); @@ -605,56 +434,6 @@ public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) } } - private static void ReadAsyncCallbackComplete(object state) - { - TaskCompletionSource source = (TaskCompletionSource)state; - source.TrySetResult(null); - } - - protected abstract bool CheckPacket(PacketHandle packet, TaskCompletionSource source); - - private void ReadAsyncCallbackCaptureException(TaskCompletionSource source) - { - bool captureSuccess = false; - try - { - if (_hasErrorOrWarning) - { - // Do the close on another thread, since we don't want to block the callback thread - ThrowExceptionAndWarning(asyncClose: true); - } - else if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken)) - { - // Connection was closed by another thread before we parsed the packet, so no error was added to the collection - throw ADP.ClosedConnectionError(); - } - } - catch (Exception ex) - { - if (source.TrySetException(ex)) - { - // There was an exception, and it was successfully stored in the task - captureSuccess = true; - } - } - - if (!captureSuccess) - { - // Either there was no exception, or the task was already completed - // This is unusual, but possible if a fatal timeout occurred on another thread (which should mean that the connection is now broken) - Debug.Assert(_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed || _parser.Connection.IsConnectionDoomed, "Failed to capture exception while the connection was still healthy"); - - // The safest thing to do is to ensure that the connection is broken and attempt to cancel the task - // This must be done from another thread to not block the callback thread - Task.Factory.StartNew(() => - { - _parser.State = TdsParserState.Broken; - _parser.Connection.BreakConnection(); - source.TrySetCanceled(); - }); - } - } - public void WriteAsyncCallback(PacketHandle packet, uint sniError) => WriteAsyncCallback(IntPtr.Zero, packet, sniError); @@ -745,242 +524,6 @@ public void WriteAsyncCallback(IntPtr key, PacketHandle packet, uint sniError) // Network/Packet Writing & Processing // ///////////////////////////////////////// - // - // Takes a secure string and offsets and saves them for a write latter when the information is written out to SNI Packet - // This method is provided to better handle the life cycle of the clear text of the secure string - // This method also ensures that the clear text is not held in the unpined managed buffer so that it avoids getting moved around by CLR garbage collector - // TdsParserStaticMethods.EncryptPassword operation is also done in the unmanaged buffer for the clear text later - // - internal void WriteSecureString(SecureString secureString) - { - TdsParser.ReliabilitySection.Assert("unreliable call to WriteSecureString"); // you need to setup for a thread abort somewhere before you call this method - - Debug.Assert(_securePasswords[0] == null || _securePasswords[1] == null, "There are more than two secure passwords"); - - int index = _securePasswords[0] != null ? 1 : 0; - - _securePasswords[index] = secureString; - _securePasswordOffsetsInBuffer[index] = _outBytesUsed; - - // loop through and write the entire array - int lengthInBytes = secureString.Length * 2; - - // It is guaranteed both secure password and secure change password should fit into the first packet - // Given current TDS format and implementation it is not possible that one of secure string is the last item and exactly fill up the output buffer - // if this ever happens and it is correct situation, the packet needs to be written out after _outBytesUsed is update - Debug.Assert((_outBytesUsed + lengthInBytes) < _outBuff.Length, "Passwords cannot be split into two different packet or the last item which fully fill up _outBuff!!!"); - - _outBytesUsed += lengthInBytes; - } - - internal void ResetSecurePasswordsInformation() - { - for (int i = 0; i < _securePasswords.Length; ++i) - { - _securePasswords[i] = null; - _securePasswordOffsetsInBuffer[i] = 0; - } - } - - internal Task WaitForAccumulatedWrites() - { - // Checked for stored exceptions - Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); - if (delayedException != null) - { - throw delayedException; - } -#pragma warning restore 420 - - if (_asyncWriteCount == 0) - { - return null; - } - - _writeCompletionSource = new TaskCompletionSource(); - Task task = _writeCompletionSource.Task; - - // Ensure that _writeCompletionSource is set before checking state - Interlocked.MemoryBarrier(); - - // Now that we have set _writeCompletionSource, check if parser is closed or broken - if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken)) - { - throw ADP.ClosedConnectionError(); - } - - // Check for stored exceptions - delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); - if (delayedException != null) - { - throw delayedException; - } - - // If there are no outstanding writes, see if we can shortcut and return null - if ((_asyncWriteCount == 0) && ((!task.IsCompleted) || (task.Exception == null))) - { - task = null; - } - - return task; - } - - // Takes in a single byte and writes it to the buffer. If the buffer is full, it is flushed - // and then the buffer is re-initialized in flush() and then the byte is put in the buffer. - internal void WriteByte(byte b) - { - TdsParser.ReliabilitySection.Assert("unreliable call to WriteByte"); // you need to setup for a thread abort somewhere before you call this method - - Debug.Assert(_outBytesUsed <= _outBuff.Length, "ERROR - TDSParser: _outBytesUsed > _outBuff.Length"); - - // check to make sure we haven't used the full amount of space available in the buffer, if so, flush it - if (_outBytesUsed == _outBuff.Length) - { - WritePacket(TdsEnums.SOFTFLUSH, canAccumulate: true); - } - // set byte in buffer and increment the counter for number of bytes used in the out buffer - _outBuff[_outBytesUsed++] = b; - } - - internal Task WriteByteSpan(ReadOnlySpan span, bool canAccumulate = true, TaskCompletionSource completion = null) - { - return WriteBytes(span, span.Length, 0, canAccumulate, completion); - } - - internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null) - { - return WriteBytes(ReadOnlySpan.Empty, len, offsetBuffer, canAccumulate, completion, b); - } - - // - // Takes a span or a byte array and writes it to the buffer - // If you pass in a span and a null array then the span wil be used. - // If you pass in a non-null array then the array will be used and the span is ignored. - // if the span cannot be written into the current packet then the remaining contents of the span are copied to a - // new heap allocated array that will used to callback into the method to continue the write operation. - private Task WriteBytes(ReadOnlySpan b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null, byte[] array = null) - { - if (array != null) - { - b = new ReadOnlySpan(array, offsetBuffer, len); - } - try - { - TdsParser.ReliabilitySection.Assert("unreliable call to WriteByteArray"); // you need to setup for a thread abort somewhere before you call this method - - bool async = _parser._asyncWrite; // NOTE: We are capturing this now for the assert after the Task is returned, since WritePacket will turn off async if there is an exception - Debug.Assert(async || _asyncWriteCount == 0); - // Do we have to send out in packet size chunks, or can we rely on netlib layer to break it up? - // would prefer to do something like: - // - // if (len > what we have room for || len > out buf) - // flush buffer - // UnsafeNativeMethods.Write(b) - // - - int offset = offsetBuffer; - - Debug.Assert(b.Length >= len, "Invalid length sent to WriteBytes()!"); - - // loop through and write the entire array - do - { - if ((_outBytesUsed + len) > _outBuff.Length) - { - // If the remainder of the data won't fit into the buffer, then we have to put - // whatever we can into the buffer, and flush that so we can then put more into - // the buffer on the next loop of the while. - - int remainder = _outBuff.Length - _outBytesUsed; - - // write the remainder - Span copyTo = _outBuff.AsSpan(_outBytesUsed, remainder); - ReadOnlySpan copyFrom = b.Slice(0, remainder); - - Debug.Assert(copyTo.Length == copyFrom.Length, $"copyTo.Length:{copyTo.Length} and copyFrom.Length{copyFrom.Length:D} should be the same"); - - copyFrom.CopyTo(copyTo); - - offset += remainder; - _outBytesUsed += remainder; - len -= remainder; - b = b.Slice(remainder, len); - - Task packetTask = WritePacket(TdsEnums.SOFTFLUSH, canAccumulate); - - if (packetTask != null) - { - Task task = null; - Debug.Assert(async, "Returned task in sync mode"); - if (completion == null) - { - completion = new TaskCompletionSource(); - task = completion.Task; // we only care about return from topmost call, so do not access Task property in other cases - } - - if (array == null) - { - byte[] tempArray = new byte[len]; - Span copyTempTo = tempArray.AsSpan(); - - Debug.Assert(copyTempTo.Length == b.Length, $"copyTempTo.Length:{copyTempTo.Length} and copyTempFrom.Length:{b.Length:D} should be the same"); - - b.CopyTo(copyTempTo); - array = tempArray; - offset = 0; - } - - WriteBytesSetupContinuation(array, len, completion, offset, packetTask); - return task; - } - } - else - { - //((stateObj._outBytesUsed + len) <= stateObj._outBuff.Length ) - // Else the remainder of the string will fit into the buffer, so copy it into the - // buffer and then break out of the loop. - - Span copyTo = _outBuff.AsSpan(_outBytesUsed, len); - ReadOnlySpan copyFrom = b.Slice(0, len); - - Debug.Assert(copyTo.Length == copyFrom.Length, $"copyTo.Length:{copyTo.Length} and copyFrom.Length:{copyFrom.Length:D} should be the same"); - - copyFrom.CopyTo(copyTo); - - // handle out buffer bytes used counter - _outBytesUsed += len; - break; - } - } while (len > 0); - - if (completion != null) - { - completion.SetResult(null); - } - return null; - } - catch (Exception e) - { - if (completion != null) - { - completion.SetException(e); - return null; - } - else - { - throw; - } - } - } - - // This is in its own method to avoid always allocating the lambda in WriteBytes - private void WriteBytesSetupContinuation(byte[] array, int len, TaskCompletionSource completion, int offset, Task packetTask) - { - AsyncHelper.ContinueTask(packetTask, completion, - onSuccess: () => WriteBytes(ReadOnlySpan.Empty, len: len, offsetBuffer: offset, canAccumulate: false, completion: completion, array) - ); - } - // Dumps contents of buffer to SNI for network write. internal Task WritePacket(byte flushMode, bool canAccumulate = false) { @@ -1051,32 +594,16 @@ internal Task WritePacket(byte flushMode, bool canAccumulate = false) if (willCancel) { // If we have been canceled, then ensure that we write the ATTN packet as well +#if NET task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket); +#else + task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket, _parser.Connection); +#endif } return task; } - private void CancelWritePacket() - { - Debug.Assert(_cancelled, "Should not call CancelWritePacket if _cancelled is not set"); - - _parser.Connection.ThreadHasParserLockForClose = true; // In case of error, let the connection know that we are holding the lock - try - { - // Send the attention and wait for the ATTN_ACK - SendAttention(); - ResetCancelAndProcessAttention(); - - // Let the caller know that we've given up - throw SQL.OperationCancelled(); - } - finally - { - _parser.Connection.ThreadHasParserLockForClose = false; - } - } - #pragma warning disable 420 // a reference to a volatile field will not be treated as volatile private Task SNIWritePacket(PacketHandle packet, out uint sniError, bool canAccumulate, bool callerHasConnectionLock, bool asyncClose = false) @@ -1212,10 +739,6 @@ private Task SNIWritePacket(PacketHandle packet, out uint sniError, bool canAccu return task; } - internal abstract bool IsValidPacket(PacketHandle packetPointer); - - internal abstract uint WritePacket(PacketHandle packet, bool sync); - // Sends an attention signal - executing thread will consume attn. internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = false) { @@ -1354,22 +877,6 @@ private Task WriteSni(bool canAccumulate) // Statistics, Tracing, and related methods // ////////////////////////////////////////////// - private void SniReadStatisticsAndTracing() - { - SqlStatistics statistics = Parser.Statistics; - if (statistics != null) - { - if (statistics.WaitForReply) - { - statistics.SafeIncrement(ref statistics._serverRoundtrips); - statistics.ReleaseAndUpdateNetworkServerTimer(); - } - - statistics.SafeAdd(ref statistics._bytesReceived, _inBytesRead); - statistics.SafeIncrement(ref statistics._buffersReceived); - } - } - private void SniWriteStatisticsAndTracing() { SqlStatistics statistics = _parser.Statistics; @@ -1380,350 +887,5 @@ private void SniWriteStatisticsAndTracing() statistics.RequestNetworkServerTimer(); } } - - [Conditional("DEBUG")] - private void AssertValidState() - { - if (_inBytesUsed < 0 || _inBytesRead < 0) - { - Debug.Fail($"Invalid TDS Parser State: either _inBytesUsed or _inBytesRead is negative: {_inBytesUsed}, {_inBytesRead}"); - } - else if (_inBytesUsed > _inBytesRead) - { - Debug.Fail($"Invalid TDS Parser State: _inBytesUsed > _inBytesRead: {_inBytesUsed} > {_inBytesRead}"); - } - - Debug.Assert(_inBytesPacket >= 0, "Packet must not be negative"); - } - - - ////////////////////////////////////////////// - // Errors and Warnings // - ////////////////////////////////////////////// - - /// - /// True if there is at least one error or warning (not counting the pre-attention errors\warnings) - /// - internal bool HasErrorOrWarning - { - get - { - return _hasErrorOrWarning; - } - } - - /// - /// Adds an error to the error collection - /// - /// - internal void AddError(SqlError error) - { - Debug.Assert(error != null, "Trying to add a null error"); - - // Switch to sync once we see an error - _syncOverAsync = true; - - lock (_errorAndWarningsLock) - { - _hasErrorOrWarning = true; - if (_errors == null) - { - _errors = new SqlErrorCollection(); - } - _errors.Add(error); - } - } - - /// - /// Gets the number of errors currently in the error collection - /// - internal int ErrorCount - { - get - { - int count = 0; - lock (_errorAndWarningsLock) - { - if (_errors != null) - { - count = _errors.Count; - } - } - return count; - } - } - - /// - /// Adds an warning to the warning collection - /// - /// - internal void AddWarning(SqlError error) - { - Debug.Assert(error != null, "Trying to add a null error"); - - // Switch to sync once we see a warning - _syncOverAsync = true; - - lock (_errorAndWarningsLock) - { - _hasErrorOrWarning = true; - if (_warnings == null) - { - _warnings = new SqlErrorCollection(); - } - _warnings.Add(error); - } - } - - /// - /// Gets the number of warnings currently in the warning collection - /// - internal int WarningCount - { - get - { - int count = 0; - lock (_errorAndWarningsLock) - { - if (_warnings != null) - { - count = _warnings.Count; - } - } - return count; - } - } - - protected abstract PacketHandle EmptyReadPacket { get; } - - internal int PreAttentionErrorCount - { - get - { - int count = 0; - lock (_errorAndWarningsLock) - { - if (_preAttentionErrors != null) - { - count = _preAttentionErrors.Count; - } - } - return count; - } - } - - /// - /// Gets the number of errors currently in the pre-attention warning collection - /// - internal int PreAttentionWarningCount - { - get - { - int count = 0; - lock (_errorAndWarningsLock) - { - if (_preAttentionWarnings != null) - { - count = _preAttentionWarnings.Count; - } - } - return count; - } - } - - /// - /// Gets the full list of errors and warnings (including the pre-attention ones), then wipes all error and warning lists - /// - /// If true, the connection should be broken - /// An array containing all of the errors and warnings - internal SqlErrorCollection GetFullErrorAndWarningCollection(out bool broken) - { - SqlErrorCollection allErrors = new SqlErrorCollection(); - broken = false; - - lock (_errorAndWarningsLock) - { - _hasErrorOrWarning = false; - - // Merge all error lists, then reset them - AddErrorsToCollection(_errors, ref allErrors, ref broken); - AddErrorsToCollection(_warnings, ref allErrors, ref broken); - _errors = null; - _warnings = null; - - // We also process the pre-attention error lists here since, if we are here and they are populated, then an error occurred while sending attention so we should show the errors now (otherwise they'd be lost) - AddErrorsToCollection(_preAttentionErrors, ref allErrors, ref broken); - AddErrorsToCollection(_preAttentionWarnings, ref allErrors, ref broken); - _preAttentionErrors = null; - _preAttentionWarnings = null; - } - - return allErrors; - } - - private void AddErrorsToCollection(SqlErrorCollection inCollection, ref SqlErrorCollection collectionToAddTo, ref bool broken) - { - if (inCollection != null) - { - foreach (SqlError error in inCollection) - { - collectionToAddTo.Add(error); - broken |= (error.Class >= TdsEnums.FATAL_ERROR_CLASS); - } - } - } - - /// - /// Stores away current errors and warnings so that an attention can be processed - /// - internal void StoreErrorAndWarningForAttention() - { - lock (_errorAndWarningsLock) - { - Debug.Assert(_preAttentionErrors == null && _preAttentionWarnings == null, "Can't store errors for attention because there are already errors stored"); - - _hasErrorOrWarning = false; - - _preAttentionErrors = _errors; - _preAttentionWarnings = _warnings; - - _errors = null; - _warnings = null; - } - } - - /// - /// Restores errors and warnings that were stored in order to process an attention - /// - internal void RestoreErrorAndWarningAfterAttention() - { - lock (_errorAndWarningsLock) - { - Debug.Assert(_errors == null && _warnings == null, "Can't restore errors after attention because there are already other errors"); - - _hasErrorOrWarning = (((_preAttentionErrors != null) && (_preAttentionErrors.Count > 0)) || ((_preAttentionWarnings != null) && (_preAttentionWarnings.Count > 0))); - - _errors = _preAttentionErrors; - _warnings = _preAttentionWarnings; - - _preAttentionErrors = null; - _preAttentionWarnings = null; - } - } - - /// - /// Checks if an error is stored in _error and, if so, throws an error - /// - internal void CheckThrowSNIException() - { - if (HasErrorOrWarning) - { - ThrowExceptionAndWarning(); - } - } - - /// - /// Debug Only: Ensures that the TdsParserStateObject has no lingering state and can safely be re-used - /// - [Conditional("DEBUG")] - internal void AssertStateIsClean() - { - // If our TdsParser is closed or broken, then we don't really care about our state - TdsParser parser = _parser; - if ((parser != null) && (parser.State != TdsParserState.Closed) && (parser.State != TdsParserState.Broken)) - { - // Async reads - Debug.Assert(_snapshot == null && !_snapshotReplay, "StateObj has leftover snapshot state"); - Debug.Assert(!_asyncReadWithoutSnapshot, "StateObj has AsyncReadWithoutSnapshot still enabled"); - Debug.Assert(_executionContext == null, "StateObj has a stored execution context from an async read"); - // Async writes - Debug.Assert(_asyncWriteCount == 0, "StateObj still has outstanding async writes"); - Debug.Assert(_delayedWriteAsyncCallbackException == null, "StateObj has an unobserved exceptions from an async write"); - // Attention\Cancellation\Timeouts - Debug.Assert(!HasReceivedAttention && !_attentionSent && !_attentionSending, $"StateObj is still dealing with attention: Sent: {_attentionSent}, Received: {HasReceivedAttention}, Sending: {_attentionSending}"); - Debug.Assert(!_cancelled, "StateObj still has cancellation set"); - Debug.Assert(_timeoutState == TimeoutState.Stopped, "StateObj still has internal timeout set"); - // Errors and Warnings - Debug.Assert(!_hasErrorOrWarning, "StateObj still has stored errors or warnings"); - } - } - -#if DEBUG - internal void CompletePendingReadWithSuccess(bool resetForcePendingReadsToWait) - { - TaskCompletionSource realNetworkPacketTaskSource = _realNetworkPacketTaskSource; - TaskCompletionSource networkPacketTaskSource = _networkPacketTaskSource; - - Debug.Assert(s_forcePendingReadsToWaitForUser, "Not forcing pends to wait for user - can't force complete"); - Debug.Assert(networkPacketTaskSource != null, "No pending read to complete"); - - try - { - if (realNetworkPacketTaskSource != null) - { - // Wait for the real read to complete - realNetworkPacketTaskSource.Task.Wait(); - } - } - finally - { - if (networkPacketTaskSource != null) - { - if (resetForcePendingReadsToWait) - { - s_forcePendingReadsToWaitForUser = false; - } - - networkPacketTaskSource.TrySetResult(null); - } - } - } - - internal void CompletePendingReadWithFailure(int errorCode, bool resetForcePendingReadsToWait) - { - TaskCompletionSource realNetworkPacketTaskSource = _realNetworkPacketTaskSource; - TaskCompletionSource networkPacketTaskSource = _networkPacketTaskSource; - - Debug.Assert(s_forcePendingReadsToWaitForUser, "Not forcing pends to wait for user - can't force complete"); - Debug.Assert(networkPacketTaskSource != null, "No pending read to complete"); - - try - { - if (realNetworkPacketTaskSource != null) - { - // Wait for the real read to complete - realNetworkPacketTaskSource.Task.Wait(); - } - } - finally - { - if (networkPacketTaskSource != null) - { - if (resetForcePendingReadsToWait) - { - s_forcePendingReadsToWaitForUser = false; - } - - AddError(new SqlError(errorCode, 0x00, TdsEnums.FATAL_ERROR_CLASS, _parser.Server, string.Empty, string.Empty, 0)); - try - { - ThrowExceptionAndWarning(); - } - catch (Exception ex) - { - networkPacketTaskSource.TrySetException(ex); - } - } - } - } -#endif - - internal void CloneCleanupAltMetaDataSetArray() - { - if (_snapshot != null) - { - _snapshot.CloneCleanupAltMetaDataSetArray(); - } - } } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs index 28617dd69c..2845ab68d0 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectManaged.cs @@ -7,20 +7,19 @@ using System; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Net.Security; using System.Runtime.CompilerServices; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; using Microsoft.Data.ProviderBase; -namespace Microsoft.Data.SqlClient.SNI +// @TODO: If this is in the manages SNI namespace, it should be in the managed SNI folder +namespace Microsoft.Data.SqlClient.ManagedSni { internal sealed class TdsParserStateObjectManaged : TdsParserStateObject { - private SNIMarsConnection? _marsConnection; - private SNIHandle? _sessionHandle; + private SniMarsConnection? _marsConnection; + private SniHandle? _sessionHandle; public TdsParserStateObjectManaged(TdsParser parser) : base(parser) { } @@ -34,7 +33,7 @@ internal TdsParserStateObjectManaged(TdsParser parser, TdsParserStateObject phys protected override bool CheckPacket(PacketHandle packet, TaskCompletionSource source) { - SNIPacket p = packet.ManagedPacket; + SniPacket p = packet.ManagedPacket; return p.IsInvalid || source != null; } @@ -52,7 +51,7 @@ protected override void CreateSessionHandle(TdsParserStateObject physicalConnect } } - internal SNIMarsHandle CreateMarsSession(object callbackObject, bool async) + internal SniMarsHandle CreateMarsSession(object callbackObject, bool async) { SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.CreateMarsSession | Info | State Object Id {0}, Session Id {1}, Async = {2}", _objectID, _sessionHandle?.ConnectionId, async); if (_marsConnection is null) @@ -69,7 +68,7 @@ internal SNIMarsHandle CreateMarsSession(object callbackObject, bool async) /// Destination byte array where data packets are copied to /// Length of data packets /// SNI error status - protected override uint SNIPacketGetData(PacketHandle packet, byte[] inBuff, ref uint dataSize) + protected override uint SniPacketGetData(PacketHandle packet, byte[] inBuff, ref uint dataSize) { int dataSizeInt = 0; packet.ManagedPacket.GetData(inBuff, ref dataSizeInt); @@ -81,7 +80,7 @@ internal override void CreatePhysicalSNIHandle( string serverName, TimeoutTimer timeout, out byte[] instanceName, - ref byte[][] spnBuffer, + out ResolvedServerSpn resolvedSpn, bool flushCache, bool async, bool parallel, @@ -94,7 +93,7 @@ internal override void CreatePhysicalSNIHandle( string hostNameInCertificate, string serverCertificateFilename) { - SNIHandle? sessionHandle = SNIProxy.CreateConnectionHandle(serverName, timeout, out instanceName, ref spnBuffer, serverSPN, + SniHandle? sessionHandle = SniProxy.CreateConnectionHandle(serverName, timeout, out instanceName, out resolvedSpn, serverSPN, flushCache, async, parallel, isIntegratedSecurity, iPAddressPreference, cachedFQDN, ref pendingDNSInfo, tlsFirst, hostNameInCertificate, serverCertificateFilename); @@ -120,9 +119,9 @@ internal override void AssignPendingDNSInfo(string userProtocol, string DNSCache // No-op } - internal void ReadAsyncCallback(SNIPacket packet, uint error) + internal void ReadAsyncCallback(SniPacket packet, uint error) { - SNIHandle? sessionHandle = _sessionHandle; + SniHandle? sessionHandle = _sessionHandle; if (sessionHandle is not null) { ReadAsyncCallback(IntPtr.Zero, PacketHandle.FromManagedPacket(packet), error); @@ -140,9 +139,9 @@ internal void ReadAsyncCallback(SNIPacket packet, uint error) } } - internal void WriteAsyncCallback(SNIPacket packet, uint sniError) + internal void WriteAsyncCallback(SniPacket packet, uint sniError) { - SNIHandle? sessionHandle = _sessionHandle; + SniHandle? sessionHandle = _sessionHandle; if (sessionHandle is not null) { WriteAsyncCallback(IntPtr.Zero, PacketHandle.FromManagedPacket(packet), sniError); @@ -167,7 +166,7 @@ protected override void RemovePacketFromPendingList(PacketHandle packet) internal override void Dispose() { - SNIHandle? sessionHandle = Interlocked.Exchange(ref _sessionHandle, null); + SniHandle? sessionHandle = Interlocked.Exchange(ref _sessionHandle, null); if (sessionHandle is not null) { SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.Dispose | Info | State Object Id {0}, Session Id {1}, Disposing session Handle and counters.", _objectID, sessionHandle.ConnectionId); @@ -204,7 +203,7 @@ protected override void FreeGcHandle(int remaining, bool release) internal override bool IsFailedHandle() { - SNIHandle? sessionHandle = _sessionHandle; + SniHandle? sessionHandle = _sessionHandle; if (sessionHandle is not null) { return sessionHandle.Status != TdsEnums.SNI_SUCCESS; @@ -215,9 +214,9 @@ internal override bool IsFailedHandle() internal override PacketHandle ReadSyncOverAsync(int timeoutRemaining, out uint error) { - SNIHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); + SniHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); - error = sessionHandle.Receive(out SNIPacket packet, timeoutRemaining); + error = sessionHandle.Receive(out SniPacket packet, timeoutRemaining); SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.ReadSyncOverAsync | Info | State Object Id {0}, Session Id {1}", _objectID, sessionHandle.ConnectionId); #if DEBUG @@ -234,14 +233,14 @@ internal override PacketHandle ReadSyncOverAsync(int timeoutRemaining, out uint internal override void ReleasePacket(PacketHandle syncReadPacket) { - SNIPacket packet = syncReadPacket.ManagedPacket; + SniPacket packet = syncReadPacket.ManagedPacket; SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.ReleasePacket | Info | State Object Id {0}, Session Id {1}, Packet DataLeft {2}", _objectID, _sessionHandle?.ConnectionId, packet?.DataLeft); #if DEBUG SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObjectManaged.ReleasePacket | TRC | State Object Id {0}, Session Id {1}, Packet {2} will be released, Packet Owner Id {3}, Packet dataLeft {4}", _objectID, _sessionHandle?.ConnectionId, packet?._id, packet?._owner.ConnectionId, packet?.DataLeft); #endif if (packet is not null) { - SNIHandle? sessionHandle = _sessionHandle; + SniHandle? sessionHandle = _sessionHandle; if (sessionHandle is not null) { sessionHandle.ReturnPacket(packet); @@ -257,13 +256,13 @@ internal override void ReleasePacket(PacketHandle syncReadPacket) internal override uint CheckConnection() { - SNIHandle? handle = GetSessionSNIHandleHandleOrThrow(); + SniHandle? handle = GetSessionSNIHandleHandleOrThrow(); return handle is null ? TdsEnums.SNI_SUCCESS : handle.CheckConnection(); } internal override PacketHandle ReadAsync(SessionHandle handle, out uint error) { - SNIPacket? packet = null; + SniPacket? packet = null; error = handle.ManagedHandle.ReceiveAsync(ref packet); SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.ReadAsync | Info | State Object Id {0}, Session Id {1}, Packet DataLeft {2}", _objectID, _sessionHandle?.ConnectionId, packet?.DataLeft); @@ -286,8 +285,8 @@ internal override PacketHandle CreateAndSetAttentionPacket() internal override uint WritePacket(PacketHandle packetHandle, bool sync) { uint result = TdsEnums.SNI_UNINITIALIZED; - SNIHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); - SNIPacket? packet = packetHandle.ManagedPacket; + SniHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); + SniPacket? packet = packetHandle.ManagedPacket; if (sync) { @@ -318,8 +317,8 @@ internal override bool IsValidPacket(PacketHandle packet) internal override PacketHandle GetResetWritePacket(int dataSize) { - SNIHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); - SNIPacket packet = sessionHandle.RentPacket(headerSize: sessionHandle.ReserveHeaderSize, dataSize: dataSize); + SniHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); + SniPacket packet = sessionHandle.RentPacket(headerSize: sessionHandle.ReserveHeaderSize, dataSize: dataSize); #if DEBUG Debug.Assert(packet.IsActive, "packet is not active, a serious pooling error may have occurred"); #endif @@ -346,7 +345,7 @@ internal override uint SniGetConnectionId(ref Guid clientConnectionId) internal override uint DisableSsl() { - SNIHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); + SniHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.DisableSsl | Info | Session Id {0}", sessionHandle.ConnectionId); sessionHandle.DisableSsl(); return TdsEnums.SNI_SUCCESS; @@ -354,8 +353,8 @@ internal override uint DisableSsl() internal override uint EnableMars(ref uint info) { - SNIHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); - _marsConnection = new SNIMarsConnection(sessionHandle); + SniHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); + _marsConnection = new SniMarsConnection(sessionHandle); SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.EnableMars | Info | State Object Id {0}, Session Id {1}", _objectID, sessionHandle.ConnectionId); if (_marsConnection.StartReceive() == TdsEnums.SNI_SUCCESS_IO_PENDING) @@ -368,7 +367,7 @@ internal override uint EnableMars(ref uint info) internal override uint EnableSsl(ref uint info, bool tlsFirst, string serverCertificateFilename) { - SNIHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); + SniHandle sessionHandle = GetSessionSNIHandleHandleOrThrow(); try { SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.EnableSsl | Info | Session Id {0}", sessionHandle.ConnectionId); @@ -377,7 +376,7 @@ internal override uint EnableSsl(ref uint info, bool tlsFirst, string serverCert catch (Exception e) { SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectManaged.EnableSsl | Err | Session Id {0}, SNI Handshake failed with exception: {1}", sessionHandle.ConnectionId, e.Message); - return SNICommon.ReportSNIError(SNIProviders.SSL_PROV, SNICommon.HandshakeFailureError, e); + return SniCommon.ReportSNIError(SniProviders.SSL_PROV, SniCommon.HandshakeFailureError, e); } } @@ -393,9 +392,9 @@ internal override uint WaitForSSLHandShakeToComplete(out int protocolVersion) return 0; } - private SNIHandle GetSessionSNIHandleHandleOrThrow() + private SniHandle GetSessionSNIHandleHandleOrThrow() { - SNIHandle? sessionHandle = _sessionHandle; + SniHandle? sessionHandle = _sessionHandle; if (sessionHandle is null) { ThrowClosedConnection(); @@ -407,7 +406,7 @@ private SNIHandle GetSessionSNIHandleHandleOrThrow() [MethodImpl(MethodImplOptions.NoInlining)] // this forces the exception throwing code not to be inlined for performance private void ThrowClosedConnection() => throw ADP.ClosedConnectionError(); - internal override SSPIContextProvider CreateSSPIContextProvider() - => new NegotiateSSPIContextProvider(); + internal override SspiContextProvider CreateSspiContextProvider() + => new NegotiateSspiContextProvider(); } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs index 62cc0e9bd4..14c2609cf3 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs @@ -43,23 +43,34 @@ private enum NativeProtocols internal SNIPacket _sniAsyncAttnPacket = null; // Packet to use to send Attn private readonly WritePacketCache _writePacketCache = new WritePacketCache(); // Store write packets that are ready to be re-used - public TdsParserStateObjectNative(TdsParser parser) : base(parser) { } - private GCHandle _gcHandle; // keeps this object alive until we're closed. - private Dictionary _pendingWritePackets = new Dictionary(); // Stores write packets that have been sent to SNI, but have not yet finished writing (i.e. we are waiting for SNI's callback) + private readonly Dictionary _pendingWritePackets = new Dictionary(); // Stores write packets that have been sent to SNI, but have not yet finished writing (i.e. we are waiting for SNI's callback) + + internal TdsParserStateObjectNative(TdsParser parser, TdsParserStateObject physicalConnection, bool async) + : base(parser, physicalConnection, async) + { + } - internal TdsParserStateObjectNative(TdsParser parser, TdsParserStateObject physicalConnection, bool async) : - base(parser, physicalConnection, async) + public TdsParserStateObjectNative(TdsParser parser) + : base(parser) { } + //////////////// + // Properties // + //////////////// + internal SNIHandle Handle => _sessionHandle; internal override uint Status => _sessionHandle != null ? _sessionHandle.Status : TdsEnums.SNI_UNINITIALIZED; internal override SessionHandle SessionHandle => SessionHandle.FromNativeHandle(_sessionHandle); + protected override PacketHandle EmptyReadPacket => PacketHandle.FromNativePointer(default); + + internal override Guid? SessionId => default; + protected override void CreateSessionHandle(TdsParserStateObject physicalConnection, bool async) { Debug.Assert(physicalConnection is TdsParserStateObjectNative, "Expected a stateObject of type " + this.GetType()); @@ -84,7 +95,7 @@ internal override void AssignPendingDNSInfo(string userProtocol, string DNSCache if (string.IsNullOrEmpty(userProtocol)) { - result = SNINativeMethodWrapper.SniGetProviderNumber(Handle, ref providerNumber); + result = SniNativeWrapper.SniGetProviderNumber(Handle, ref providerNumber); Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetProviderNumber"); _parser.isTcpProtocol = (providerNumber == Provider.TCP_PROV); } @@ -96,10 +107,10 @@ internal override void AssignPendingDNSInfo(string userProtocol, string DNSCache // serverInfo.UserProtocol could be empty if (_parser.isTcpProtocol) { - result = SNINativeMethodWrapper.SniGetConnectionPort(Handle, ref portFromSNI); + result = SniNativeWrapper.SniGetConnectionPort(Handle, ref portFromSNI); Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionPort"); - result = SNINativeMethodWrapper.SniGetConnectionIPString(Handle, ref IPStringFromSNI); + result = SniNativeWrapper.SniGetConnectionIpString(Handle, ref IPStringFromSNI); Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionIPString"); pendingDNSInfo = new SQLDNSInfo(DNSCacheKey, null, null, portFromSNI.ToString()); @@ -144,7 +155,7 @@ internal override void CreatePhysicalSNIHandle( string serverName, TimeoutTimer timeout, out byte[] instanceName, - ref byte[][] spnBuffer, + out Microsoft.Data.SqlClient.ManagedSni.ResolvedServerSpn resolvedSpn, bool flushCache, bool async, bool fParallel, @@ -157,22 +168,18 @@ internal override void CreatePhysicalSNIHandle( string hostNameInCertificate, string serverCertificateFilename) { - // We assume that the loadSSPILibrary has been called already. now allocate proper length of buffer - spnBuffer = new byte[1][]; if (isIntegratedSecurity) { // now allocate proper length of buffer if (!string.IsNullOrEmpty(serverSPN)) { // Native SNI requires the Unicode encoding and any other encoding like UTF8 breaks the code. - byte[] srvSPN = Encoding.Unicode.GetBytes(serverSPN); - Trace.Assert(srvSPN.Length <= SNINativeMethodWrapper.SniMaxComposedSpnLength, "Length of the provided SPN exceeded the buffer size."); - spnBuffer[0] = srvSPN; SqlClientEventSource.Log.TryTraceEvent("<{0}.{1}|SEC> Server SPN `{2}` from the connection string is used.", nameof(TdsParserStateObjectNative), nameof(CreatePhysicalSNIHandle), serverSPN); } else { - spnBuffer[0] = new byte[SNINativeMethodWrapper.SniMaxComposedSpnLength]; + // This will signal to the interop layer that we need to retrieve the SPN + serverSPN = string.Empty; } } @@ -180,14 +187,15 @@ internal override void CreatePhysicalSNIHandle( SQLDNSInfo cachedDNSInfo; bool ret = SQLFallbackDNSCache.Instance.GetDNSInfo(cachedFQDN, out cachedDNSInfo); - _sessionHandle = new SNIHandle(myInfo, serverName, spnBuffer[0], timeout.MillisecondsRemainingInt, out instanceName, + _sessionHandle = new SNIHandle(myInfo, serverName, ref serverSPN, timeout.MillisecondsRemainingInt, out instanceName, flushCache, !async, fParallel, ipPreference, cachedDNSInfo, hostNameInCertificate); + resolvedSpn = new(serverSPN.TrimEnd()); } - protected override uint SNIPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize) + protected override uint SniPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize) { Debug.Assert(packet.Type == PacketHandle.NativePointerType, "unexpected packet type when requiring NativePointer"); - return SNINativeMethodWrapper.SNIPacketGetData(packet.NativePointer, _inBuff, ref dataSize); + return SniNativeWrapper.SniPacketGetData(packet.NativePointer, _inBuff, ref dataSize); } protected override bool CheckPacket(PacketHandle packet, TaskCompletionSource source) @@ -259,22 +267,6 @@ protected override void FreeGcHandle(int remaining, bool release) internal override bool IsFailedHandle() => _sessionHandle.Status != TdsEnums.SNI_SUCCESS; - internal override PacketHandle ReadSyncOverAsync(int timeoutRemaining, out uint error) - { - SNIHandle handle = Handle; - if (handle == null) - { - throw ADP.ClosedConnectionError(); - } - IntPtr readPacketPtr = IntPtr.Zero; - error = SNINativeMethodWrapper.SNIReadSyncOverAsync(handle, ref readPacketPtr, GetTimeoutRemaining()); - return PacketHandle.FromNativePointer(readPacketPtr); - } - - protected override PacketHandle EmptyReadPacket => PacketHandle.FromNativePointer(default); - - internal override Guid? SessionId => default; - internal override bool IsPacketEmpty(PacketHandle readPacket) { Debug.Assert(readPacket.Type == PacketHandle.NativePointerType || readPacket.Type == 0, "unexpected packet type when requiring NativePointer"); @@ -284,20 +276,28 @@ internal override bool IsPacketEmpty(PacketHandle readPacket) internal override void ReleasePacket(PacketHandle syncReadPacket) { Debug.Assert(syncReadPacket.Type == PacketHandle.NativePointerType, "unexpected packet type when requiring NativePointer"); - SNINativeMethodWrapper.SNIPacketRelease(syncReadPacket.NativePointer); + SniNativeWrapper.SniPacketRelease(syncReadPacket.NativePointer); } internal override uint CheckConnection() { SNIHandle handle = Handle; - return handle == null ? TdsEnums.SNI_SUCCESS : SNINativeMethodWrapper.SNICheckConnection(handle); + return handle == null ? TdsEnums.SNI_SUCCESS : SniNativeWrapper.SniCheckConnection(handle); } internal override PacketHandle ReadAsync(SessionHandle handle, out uint error) { Debug.Assert(handle.Type == SessionHandle.NativeHandleType, "unexpected handle type when requiring NativePointer"); IntPtr readPacketPtr = IntPtr.Zero; - error = SNINativeMethodWrapper.SNIReadAsync(handle.NativeHandle, ref readPacketPtr); + error = SniNativeWrapper.SniReadAsync(handle.NativeHandle, ref readPacketPtr); + return PacketHandle.FromNativePointer(readPacketPtr); + } + + internal override PacketHandle ReadSyncOverAsync(int timeoutRemaining, out uint error) + { + SNIHandle handle = Handle ?? throw ADP.ClosedConnectionError(); + IntPtr readPacketPtr = IntPtr.Zero; + error = SniNativeWrapper.SniReadSyncOverAsync(handle, ref readPacketPtr, GetTimeoutRemaining()); return PacketHandle.FromNativePointer(readPacketPtr); } @@ -313,7 +313,7 @@ internal override PacketHandle CreateAndSetAttentionPacket() internal override uint WritePacket(PacketHandle packet, bool sync) { Debug.Assert(packet.Type == PacketHandle.NativePacketType, "unexpected packet type when requiring NativePacket"); - return SNINativeMethodWrapper.SNIWritePacket(Handle, packet.NativePacket, sync); + return SniNativeWrapper.SniWritePacket(Handle, packet.NativePacket, sync); } internal override PacketHandle AddPacketToPendingList(PacketHandle packetToAdd) @@ -335,18 +335,16 @@ internal override PacketHandle AddPacketToPendingList(PacketHandle packetToAdd) internal override bool IsValidPacket(PacketHandle packetPointer) { Debug.Assert(packetPointer.Type == PacketHandle.NativePointerType || packetPointer.Type == PacketHandle.NativePacketType, "unexpected packet type when requiring NativePointer"); - return ( - (packetPointer.Type == PacketHandle.NativePointerType && packetPointer.NativePointer != IntPtr.Zero) - || - (packetPointer.Type == PacketHandle.NativePacketType && packetPointer.NativePacket != null) - ); + + return (packetPointer.Type == PacketHandle.NativePointerType && packetPointer.NativePointer != IntPtr.Zero) + || (packetPointer.Type == PacketHandle.NativePacketType && packetPointer.NativePacket != null); } internal override PacketHandle GetResetWritePacket(int dataSize) { if (_sniPacket != null) { - SNINativeMethodWrapper.SNIPacketReset(Handle, IoType.WRITE, _sniPacket, ConsumerNumber.SNI_Consumer_SNI); + SniNativeWrapper.SniPacketReset(Handle, IoType.WRITE, _sniPacket, ConsumerNumber.SNI_Consumer_SNI); } else { @@ -375,17 +373,17 @@ internal override void ClearAllWritePackets() internal override void SetPacketData(PacketHandle packet, byte[] buffer, int bytesUsed) { Debug.Assert(packet.Type == PacketHandle.NativePacketType, "unexpected packet type when requiring NativePacket"); - SNINativeMethodWrapper.SNIPacketSetData(packet.NativePacket, buffer, bytesUsed); + SniNativeWrapper.SniPacketSetData(packet.NativePacket, buffer, bytesUsed); } internal override uint SniGetConnectionId(ref Guid clientConnectionId) - => SNINativeMethodWrapper.SniGetConnectionId(Handle, ref clientConnectionId); + => SniNativeWrapper.SniGetConnectionId(Handle, ref clientConnectionId); internal override uint DisableSsl() - => SNINativeMethodWrapper.SNIRemoveProvider(Handle, Provider.SSL_PROV); + => SniNativeWrapper.SniRemoveProvider(Handle, Provider.SSL_PROV); internal override uint EnableMars(ref uint info) - => SNINativeMethodWrapper.SNIAddProvider(Handle, Provider.SMUX_PROV, ref info); + => SniNativeWrapper.SniAddProvider(Handle, Provider.SMUX_PROV, ref info); internal override uint EnableSsl(ref uint info, bool tlsFirst, string serverCertificateFilename) { @@ -395,15 +393,15 @@ internal override uint EnableSsl(ref uint info, bool tlsFirst, string serverCert authInfo.serverCertFileName = serverCertificateFilename; // Add SSL (Encryption) SNI provider. - return SNINativeMethodWrapper.SNIAddProvider(Handle, Provider.SSL_PROV, ref authInfo); + return SniNativeWrapper.SniAddProvider(Handle, Provider.SSL_PROV, ref authInfo); } internal override uint SetConnectionBufferSize(ref uint unsignedPacketSize) - => SNINativeMethodWrapper.SNISetInfo(Handle, QueryType.SNI_QUERY_CONN_BUFSIZE, ref unsignedPacketSize); + => SniNativeWrapper.SniSetInfo(Handle, QueryType.SNI_QUERY_CONN_BUFSIZE, ref unsignedPacketSize); internal override uint WaitForSSLHandShakeToComplete(out int protocolVersion) { - uint returnValue = SNINativeMethodWrapper.SNIWaitForSSLHandshakeToComplete(Handle, GetTimeoutRemaining(), out uint nativeProtocolVersion); + uint returnValue = SniNativeWrapper.SniWaitForSslHandshakeToComplete(Handle, GetTimeoutRemaining(), out uint nativeProtocolVersion); var nativeProtocol = (NativeProtocols)nativeProtocolVersion; #pragma warning disable CA5398 // Avoid hardcoded SslProtocols values @@ -426,7 +424,7 @@ internal override uint WaitForSSLHandShakeToComplete(out int protocolVersion) } else if (nativeProtocol.HasFlag(NativeProtocols.SP_PROT_SSL3_CLIENT) || nativeProtocol.HasFlag(NativeProtocols.SP_PROT_SSL3_SERVER)) { -// SSL 2.0 and 3.0 are only referenced to log a warning, not explicitly used for connections + // SSL 2.0 and 3.0 are only referenced to log a warning, not explicitly used for connections #pragma warning disable CS0618, CA5397 protocolVersion = (int)SslProtocols.Ssl3; } @@ -447,12 +445,20 @@ internal override void DisposePacketCache() { lock (_writePacketLockObject) { - _writePacketCache.Dispose(); - // Do not set _writePacketCache to null, just in case a WriteAsyncCallback completes after this point +#if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); +#endif + try + { } + finally + { + _writePacketCache.Dispose(); + // Do not set _writePacketCache to null, just in case a WriteAsyncCallback completes after this point + } } } - internal override SSPIContextProvider CreateSSPIContextProvider() => new NativeSSPIContextProvider(); + internal override SspiContextProvider CreateSspiContextProvider() => new NativeSspiContextProvider(); internal sealed class WritePacketCache : IDisposable { @@ -472,7 +478,7 @@ public SNIPacket Take(SNIHandle sniHandle) { // Success - reset the packet packet = _packets.Pop(); - SNINativeMethodWrapper.SNIPacketReset(sniHandle, IoType.WRITE, packet, ConsumerNumber.SNI_Consumer_SNI); + SniNativeWrapper.SniPacketReset(sniHandle, IoType.WRITE, packet, ConsumerNumber.SNI_Consumer_SNI); } else { diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml b/src/Microsoft.Data.SqlClient/netcore/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml deleted file mode 100644 index 73ddc2f575..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml +++ /dev/null @@ -1,2539 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - MetaDataCollections - 0 - 0 - DataTable - MetaDataCollections - - - DataSourceInformation - 0 - 0 - DataTable - DataSourceInformation - - - DataTypes - 0 - 0 - PrepareCollection - - - Restrictions - 0 - 0 - DataTable - Restrictions - - - ReservedWords - 0 - 0 - DataTable - ReservedWords - - - Users - 1 - 1 - SQLCommand - select uid, name as user_name, createdate, updatedate from sysusers where (name = @Name or (@Name is null)) - - - Databases - 1 - 1 - SQLCommand - select name as database_name, dbid, crdate as create_date from master..sysdatabases where (name = @Name or (@Name is null)) - 09.99.999.9 - - - Databases - 1 - 1 - SQLCommand - IF OBJECT_ID('master..sysdatabases') IS NULL EXEC sp_executesql N'select name as database_name, dbid, crdate as create_date from sysdatabases where (name = @Name or (@Name is null))',N'@Name NVARCHAR(128)',@Name=@Name ELSE EXEC sp_executesql N'select name as database_name, dbid, crdate as create_date from master..sysdatabases where (name = @Name or (@Name is null))',N'@Name NVARCHAR(128)',@Name=@Name - 10.00.000.0 - - - Tables - 4 - 3 - SQLCommand - select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE from INFORMATION_SCHEMA.TABLES where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Name or (@Name is null)) and (TABLE_TYPE = @TableType or (@TableType is null)) - - - Columns - 4 - 4 - SQLCommand - select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, CHARACTER_SET_CATALOG, CHARACTER_SET_SCHEMA, CHARACTER_SET_NAME, COLLATION_CATALOG from INFORMATION_SCHEMA.COLUMNS where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) and (COLUMN_NAME = @Column or (@Column is null)) order by TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME - 09.99.999.9 - - - Columns - 4 - 4 - SQLCommand - EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, @Column, 0 - 10.00.000.0 - - - AllColumns - 4 - 4 - SQLCommand - EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, @Column, 1 - 10.00.000.0 - - - ColumnSetColumns - 3 - 3 - SQLCommand - EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, null, 2 - 10.00.000.0 - - - StructuredTypeMembers - 4 - 4 - SQLCommand - SELECT DB_NAME() AS TYPE_CATALOG, sc.name AS TYPE_SCHEMA, tt.name AS TYPE_NAME, c.name AS MEMBER_NAME, ColumnProperty(c.object_id, c.name, 'ordinal') AS ORDINAL_POSITION, convert(nvarchar(4000), object_definition(c.default_object_id)) AS MEMBER_DEFAULT, convert(varchar(3), CASE c.is_nullable WHEN 1 THEN 'YES' ELSE 'NO' END) AS IS_NULLABLE, type_name(c.system_type_id) AS DATA_TYPE, ColumnProperty(c.object_id, c.name, 'charmaxlen') AS CHARACTER_MAXIMUM_LENGTH, ColumnProperty(c.object_id, c.name, 'octetmaxlen') AS CHARACTER_OCTET_LENGTH, convert(tinyint, CASE WHEN c.system_type_id IN /* int/decimal/numeric/real/float/money */ (48, 52, 56, 59, 60, 62, 106, 108, 122, 127) THEN c.precision END) AS NUMERIC_PRECISION, convert(smallint, CASE WHEN c.system_type_id IN /* int/money/decimal/numeric */ (48, 52, 56, 60, 106, 108, 122, 127) THEN 10 WHEN c.system_type_id IN /* real/float */ (59, 62) THEN 2 END) AS NUMERIC_PRECISION_RADIX, convert(int, CASE WHEN c.system_type_id IN /* datetime/smalldatetime */ (58, 61) THEN NULL ELSE odbcscale(c.system_type_id, c.scale) END) AS NUMERIC_SCALE, convert(smallint, CASE WHEN c.system_type_id IN /* datetime/smalldatetime */ (58, 61) THEN 3 END) AS DATETIME_PRECISION, convert(sysname, null) AS CHARACTER_SET_CATALOG, convert(sysname, null) AS CHARACTER_SET_SCHEMA, convert(sysname, CASE WHEN c.system_type_id IN /* char/varchar/text */ (35, 167, 175) THEN CollationProperty(c.collation_name, 'sqlcharsetname') WHEN c.system_type_id IN /* nchar/nvarchar/ntext */ (99, 231, 239) THEN N'UNICODE' END) AS CHARACTER_SET_NAME, convert(sysname, null) AS COLLATION_CATALOG FROM sys.table_types tt join sys.objects o on o.object_id = tt.type_table_object_id JOIN sys.schemas sc on sc.schema_id = tt.schema_id JOIN sys.columns c ON c.object_id = o.object_id LEFT JOIN sys.types t ON c.user_type_id = t.user_type_id WHERE o.type IN ('TT') AND (DB_NAME() = @Catalog or (@Catalog is null)) and (sc.name = @Owner or (@Owner is null)) and (tt.name = @Type or (@Type is null)) and (c.name = @Member or (@Member is null)) order by sc.name, tt.name, c.name - 09.00.000.0 - - - Views - 3 - 3 - SQLCommand - select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, CHECK_OPTION, IS_UPDATABLE from INFORMATION_SCHEMA.VIEWS where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) order by TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME - 08.00.000.0 - - - ViewColumns - 4 - 4 - SQLCommand - select VIEW_CATALOG, VIEW_SCHEMA, VIEW_NAME, TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME from INFORMATION_SCHEMA.VIEW_COLUMN_USAGE where (VIEW_CATALOG = @Catalog or (@Catalog is null)) and (VIEW_SCHEMA = @Owner or (@Owner is null)) and (VIEW_NAME = @Table or (@Table is null)) and (COLUMN_NAME = @Column or (@Column is null)) order by VIEW_CATALOG, VIEW_SCHEMA, VIEW_NAME - 08.00.000.0 - - - ProcedureParameters - 4 - 1 - SQLCommand - select SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, ORDINAL_POSITION, PARAMETER_MODE, IS_RESULT, AS_LOCATOR, PARAMETER_NAME, CASE WHEN DATA_TYPE IS NULL THEN USER_DEFINED_TYPE_NAME WHEN DATA_TYPE = 'table type' THEN USER_DEFINED_TYPE_NAME ELSE DATA_TYPE END as DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH, COLLATION_CATALOG, COLLATION_SCHEMA, COLLATION_NAME, CHARACTER_SET_CATALOG, CHARACTER_SET_SCHEMA, CHARACTER_SET_NAME, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, INTERVAL_TYPE, INTERVAL_PRECISION from INFORMATION_SCHEMA.PARAMETERS where (SPECIFIC_CATALOG = @Catalog or (@Catalog is null)) and (SPECIFIC_SCHEMA = @Owner or (@Owner is null)) and (SPECIFIC_NAME = @Name or (@Name is null)) and (PARAMETER_NAME = @Parameter or (@Parameter is null)) order by SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, PARAMETER_NAME - 08.00.0000 - - - Procedures - 4 - 3 - SQLCommand - select SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, ROUTINE_CATALOG, ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE, CREATED, LAST_ALTERED from INFORMATION_SCHEMA.ROUTINES where (SPECIFIC_CATALOG = @Catalog or (@Catalog is null)) and (SPECIFIC_SCHEMA = @Owner or (@Owner is null)) and (SPECIFIC_NAME = @Name or (@Name is null)) and (ROUTINE_TYPE = @Type or (@Type is null)) order by SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME - 08.00.0000 - - - ForeignKeys - 4 - 3 - SQLCommand - select CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME, TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, CONSTRAINT_TYPE, IS_DEFERRABLE, INITIALLY_DEFERRED from INFORMATION_SCHEMA.TABLE_CONSTRAINTS where (CONSTRAINT_CATALOG = @Catalog or (@Catalog is null)) and (CONSTRAINT_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) and (CONSTRAINT_NAME = @Name or (@Name is null)) and CONSTRAINT_TYPE = 'FOREIGN KEY' order by CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME - - - IndexColumns - 5 - 4 - SQLCommand - select distinct db_Name() as constraint_catalog, constraint_schema = user_name(o.uid), constraint_name = x.name, table_catalog = db_name(), table_schema = user_name(o.uid), table_name = o.name, column_name = c.name, ordinal_position = convert(int, xk.keyno), KeyType = c.xtype, index_name = x.name from sysobjects o, sysindexes x, syscolumns c, sysindexkeys xk where o.type in ('U') and x.id = o.id and o.id = c.id and o.id = xk.id and x.indid = xk.indid and c.colid = xk.colid and xk.keyno < = x.keycnt and permissions(o.id, c.name) <> 0 and (db_name() = @Catalog or (@Catalog is null)) and (user_name()= @Owner or (@Owner is null)) and (o.name = @Table or (@Table is null)) and (x.name = @ConstraintName or (@ConstraintName is null)) and (c.name = @Column or (@Column is null)) order by table_name, index_name - 09.99.9999 - - - IndexColumns - 5 - 4 - SQLCommand - EXEC sys.sp_indexcolumns_managed @Catalog, @Owner, @Table, @ConstraintName, @Column - 10.00.0000 - - - Indexes - 4 - 3 - SQLCommand - select distinct db_Name() as constraint_catalog, constraint_schema = user_name(o.uid), constraint_name = x.name, table_catalog = db_name(), table_schema = user_name(o.uid), table_name = o.name, index_name = x.name from sysobjects o, sysindexes x, sysindexkeys xk where o.type in ('U') and x.id = o.id and o.id = xk.id and x.indid = xk.indid and xk.keyno < = x.keycnt and (db_name() = @Catalog or (@Catalog is null)) and (user_name()= @Owner or (@Owner is null)) and (o.name = @Table or (@Table is null)) and (x.name = @Name or (@Name is null)) order by table_name, index_name - 09.99.9999 - - - Indexes - 4 - 3 - SQLCommand - EXEC sys.sp_indexes_managed @Catalog, @Owner, @Table, @Name - 10.00.0000 - - - UserDefinedTypes - 2 - 1 - SQLCommand - select assemblies.name as assembly_name, types.assembly_class as udt_name, ASSEMBLYPROPERTY(assemblies.name, 'VersionMajor') as version_major, ASSEMBLYPROPERTY(assemblies.name, 'VersionMinor') as version_minor, ASSEMBLYPROPERTY(assemblies.name, 'VersionBuild') as version_build, ASSEMBLYPROPERTY(assemblies.name, 'VersionRevision') as version_revision, ASSEMBLYPROPERTY(assemblies.name, 'CultureInfo') as culture_info, ASSEMBLYPROPERTY(assemblies.name, 'PublicKey') as public_key, is_fixed_length, max_length, Create_Date, Permission_set_desc from sys.assemblies as assemblies join sys.assembly_types as types on assemblies.assembly_id = types.assembly_id where (assemblies.name = @AssemblyName or (@AssemblyName is null)) and (types.assembly_class = @UDTName or (@UDTName is null)) - 09.00.0000 - - - Users - User_Name - @Name - name - 1 - - - Databases - Name - @Name - Name - 1 - - - Tables - Catalog - @Catalog - TABLE_CATALOG - 1 - - - Tables - Owner - @Owner - TABLE_SCHEMA - 2 - - - Tables - Table - @Name - TABLE_NAME - 3 - - - Tables - TableType - @TableType - TABLE_TYPE - 4 - - - Columns - Catalog - @Catalog - TABLE_CATALOG - 1 - - - Columns - Owner - @Owner - TABLE_SCHEMA - 2 - - - Columns - Table - @Table - TABLE_NAME - 3 - - - Columns - Column - @Column - COLUMN_NAME - 4 - - - AllColumns - Catalog - @Catalog - TABLE_CATALOG - 1 - - - AllColumns - Owner - @Owner - TABLE_SCHEMA - 2 - - - AllColumns - Table - @Table - TABLE_NAME - 3 - - - AllColumns - Column - @Column - COLUMN_NAME - 4 - - - ColumnSetColumns - Catalog - @Catalog - TABLE_CATALOG - 1 - - - ColumnSetColumns - Owner - @Owner - TABLE_SCHEMA - 2 - - - ColumnSetColumns - Table - @Table - TABLE_NAME - 3 - - - StructuredTypeMembers - Catalog - @Catalog - TYPE_CATALOG - 1 - - - StructuredTypeMembers - Owner - @Owner - TYPE_SCHEMA - 2 - - - StructuredTypeMembers - Type - @Type - TYPE_NAME - 3 - - - StructuredTypeMembers - Member - @Member - MEMBER_NAME - 4 - - - Views - Catalog - @Catalog - TABLE_CATALOG - 1 - 08.00.000.0 - - - Views - Owner - @Owner - TABLE_SCHEMA - 2 - 08.00.000.0 - - - Views - Table - @Table - TABLE_NAME - 3 - 08.00.000.0 - - - ViewColumns - Catalog - @Catalog - VIEW_CATALOG - 1 - 08.00.000.0 - - - ViewColumns - Owner - @Owner - VIEW_SCHEMA - 2 - 08.00.000.0 - - - ViewColumns - Table - @Table - VIEW_NAME - 3 - 08.00.000.0 - - - ViewColumns - Column - @Column - COLUMN_NAME - 4 - 08.00.000.0 - - - ProcedureParameters - Catalog - @Catalog - SPECIFIC_CATALOG - 1 - 08.00.0000 - - - ProcedureParameters - Owner - @Owner - SPECIFIC_SCHEMA - 2 - 08.00.0000 - - - ProcedureParameters - Name - @Name - SPECIFIC_NAME - 3 - 08.00.0000 - - - ProcedureParameters - Parameter - @Parameter - PARAMETER_NAME - 4 - 08.00.0000 - - - Procedures - Catalog - @Catalog - SPECIFIC_CATALOG - 1 - 08.00.0000 - - - Procedures - Owner - @Owner - SPECIFIC_SCHEMA - 2 - 08.00.0000 - - - Procedures - Name - @Name - SPECIFIC_NAME - 3 - 08.00.0000 - - - Procedures - Type - @Type - ROUTINE_TYPE - 4 - 08.00.0000 - - - IndexColumns - Catalog - @Catalog - db_name() - 1 - - - IndexColumns - Owner - @Owner - user_name() - 2 - - - IndexColumns - Table - @Table - o.name - 3 - - - IndexColumns - ConstraintName - @ConstraintName - x.name - 4 - - - IndexColumns - Column - @Column - c.name - 5 - - - Indexes - Catalog - @Catalog - db_name() - 1 - - - Indexes - Owner - @Owner - user_name() - 2 - - - Indexes - Table - @Table - o.name - 3 - - - Indexes - Name - @Name - x.name - 4 - - - UserDefinedTypes - assembly_name - @AssemblyName - assemblies.name - 1 - 09.00.000.0 - - - UserDefinedTypes - udt_name - @UDTName - types.assembly_class - 2 - 09.00.000.0 - - - ForeignKeys - Catalog - @Catalog - CONSTRAINT_CATALOG - 1 - - - ForeignKeys - Owner - @Owner - CONSTRAINT_SCHEMA - 2 - - - ForeignKeys - Table - @Table - TABLE_NAME - 3 - - - ForeignKeys - Name - @Name - CONSTRAINT_NAME - 4 - - - \. - Microsoft SQL Server - 2 - (^\[\p{Lo}\p{Lu}\p{Ll}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Nd}@$#_]*$)|(^\[[^\]\0]|\]\]+\]$)|(^\"[^\"\0]|\"\"+\"$) - 1 - false - {0} - @[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$) - 128 - ^[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$) - (([^\[]|\]\])*) - 1 - ; - '(([^']|'')*)' - 15 - - -smallint -16 -5 -smallint -System.Int16 -true -true -false -false -true -true -false -true -true -false -false - - -int -8 -10 -int -System.Int32 -true -true -false -false -true -true -false -true -true -false -false - - -real -13 -7 -real -System.Single -false -true -false -false -true -false -false -true -true -false -false - - -float -6 -53 -float({0}) -number of bits used to store the mantissa -System.Double -false -true -false -false -true -false -false -true -true -false -false - - -money -9 -19 -money -System.Decimal -false -false -false -false -true -true -false -true -true -false -false - - -smallmoney -17 -10 -smallmoney -System.Decimal -false -false -false -false -true -true -false -true -true -false -false - - -bit -2 -1 -bit -System.Boolean -false -false -false -false -true -false -false -true -true -false - - -tinyint -20 -3 -tinyint -System.Byte -true -true -false -false -true -true -false -true -true -false -true - - -bigint -0 -19 -bigint -System.Int64 -true -true -false -false -true -true -false -true -true -false -false - - -varbinary -21 -8000 -varbinary({0}) -max length -System.Byte[] -false -true -false -false -false -false -false -true -true -false -08.99.999.9 -0x - - -timestamp -19 -8 -timestamp -System.Byte[] -false -false -false -true -true -false -false -false -true -false -0x - - -binary -1 -8000 -binary({0}) -length -System.Byte[] -false -true -false -false -true -false -false -true -true -false -0x - - -image -7 -2147483647 -image -System.Byte[] -false -true -false -false -false -false -true -true -false -false -0x - - -char -3 -8000 -char -length -System.String -false -true -false -false -true -false -false -true -true -true -08.99.999.9 -' -' - - -text -18 -2147483647 -text -System.String -false -true -false -false -false -false -true -true -false -true -' -' - - -varchar -22 -8000 -varchar({0}) -max length -System.String -false -true -false -false -false -false -false -true -true -true -08.99.999.9 -' -' - - -nchar -10 -4000 -nchar({0}) -length -System.String -false -true -false -false -true -false -false -true -true -true -08.99.999.9 -N' -' - - -ntext -11 -1073741823 -ntext -System.String -false -true -false -false -false -false -true -true -false -true -N' -' - - -nvarchar -12 -4000 -nvarchar({0}) -max length -System.String -false -true -false -false -false -false -false -true -true -true -08.99.999.9 -N' -' - - -decimal -5 -38 -decimal({0}, {1}) -precision,scale -System.Decimal -true -true -false -false -true -false -false -true -true -false -false -38 -0 - - -numeric -5 -38 -numeric({0}, {1}) -precision,scale -System.Decimal -true -true -false -false -true -false -false -true -true -false -false -38 -0 - - -datetime -4 -23 -datetime -System.DateTime -false -true -false -false -true -false -false -true -true -true -{ts ' -'} - - -smalldatetime -15 -16 -smalldatetime -System.DateTime -false -true -false -false -true -false -false -true -true -true -{ts ' -'} - - -sql_variant -23 -sql_variant -System.Object -false -true -false -false -false -false -false -true -true -false -false - - -xml -25 -2147483647 -xml -System.String -false -false -false -false -false -false -true -true -false -false -09.00.000.0 -false - - -varchar -22 -2147483647 -varchar({0}) -max length -System.String -false -true -false -false -false -false -false -true -true -true -09.00.000.0 -' -' - - -char -3 -2147483647 -char({0}) -length -System.String -false -true -false -false -true -false -false -true -true -true -09.00.000.0 -' -' - - -nchar -10 -1073741823 -nchar({0}) -length -System.String -false -true -false -false -true -false -false -true -true -true -09.00.000.0 -N' -' - - -nvarchar -12 -1073741823 -nvarchar({0}) -max length -System.String -false -true -false -false -false -false -false -true -true -true -09.00.000.0 -N' -' - - -varbinary -21 -1073741823 -varbinary({0}) -max length -System.Byte[] -false -true -false -false -false -false -false -true -true -false -09.00.000.0 -0x - - -uniqueidentifier -14 -16 -uniqueidentifier -System.Guid -false -true -false -false -true -false -false -true -true -false -' -' - - -date -31 -3 -date -System.DateTime -false -false -false -false -true -true -false -true -true -true -10.00.000.0 -{ts ' -'} - - -time -32 -5 -time({0}) -scale -System.TimeSpan -false -false -false -false -false -false -false -true -true -true -7 -0 -10.00.000.0 -{ts ' -'} - - -datetime2 -33 -8 -datetime2({0}) -scale -System.DateTime -false -true -false -false -false -false -false -true -true -true -7 -0 -10.00.000.0 -{ts ' -'} - - -datetimeoffset -34 -10 -datetimeoffset({0}) -scale -System.DateTimeOffset -false -true -false -false -false -false -false -true -true -true -7 -0 -10.00.000.0 -{ts ' -'} - - - ADD - - - EXCEPT - - - PERCENT - - - ALL - - - EXEC - - - PLAN - - - ALTER - - - EXECUTE - - - PRECISION - - - AND - - - EXISTS - - - PRIMARY - - - ANY - - - EXIT - - - PRINT - - - AS - - - FETCH - - - PROC - - - ASC - - - FILE - - - PROCEDURE - - - AUTHORIZATION - - - FILLFACTOR - - - PUBLIC - - - BACKUP - - - FOR - - - RAISERROR - - - BEGIN - - - FOREIGN - - - READ - - - BETWEEN - - - FREETEXT - - - READTEXT - - - BREAK - - - FREETEXTTABLE - - - RECONFIGURE - - - BROWSE - - - FROM - - - REFERENCES - - - BULK - - - FULL - - - REPLICATION - - - BY - - - FUNCTION - - - RESTORE - - - CASCADE - - - GOTO - - - RESTRICT - - - CASE - - - GRANT - - - RETURN - - - CHECK - - - GROUP - - - REVOKE - - - CHECKPOINT - - - HAVING - - - RIGHT - - - CLOSE - - - HOLDLOCK - - - ROLLBACK - - - CLUSTERED - - - IDENTITY - - - ROWCOUNT - - - COALESCE - - - IDENTITY_INSERT - - - ROWGUIDCOL - - - COLLATE - - - IDENTITYCOL - - - RULE - - - COLUMN - - - IF - - - SAVE - - - COMMIT - - - IN - - - SCHEMA - - - COMPUTE - - - INDEX - - - SELECT - - - CONSTRAINT - - - INNER - - - SESSION_USER - - - CONTAINS - - - INSERT - - - SET - - - CONTAINSTABLE - - - INTERSECT - - - SETUSER - - - CONTINUE - - - INTO - - - SHUTDOWN - - - CONVERT - - - IS - - - SOME - - - CREATE - - - JOIN - - - STATISTICS - - - CROSS - - - KEY - - - SYSTEM_USER - - - CURRENT - - - KILL - - - TABLE - - - CURRENT_DATE - - - LEFT - - - TEXTSIZE - - - CURRENT_TIME - - - LIKE - - - THEN - - - CURRENT_TIMESTAMP - - - LINENO - - - TO - - - CURRENT_USER - - - LOAD - - - TOP - - - CURSOR - - - NATIONAL - - - TRAN - - - DATABASE - - - NOCHECK - - - TRANSACTION - - - DBCC - - - NONCLUSTERED - - - TRIGGER - - - DEALLOCATE - - - NOT - - - TRUNCATE - - - DECLARE - - - NULL - - - TSEQUAL - - - DEFAULT - - - NULLIF - - - UNION - - - DELETE - - - OF - - - UNIQUE - - - DENY - - - OFF - - - UPDATE - - - DESC - - - OFFSETS - - - UPDATETEXT - - - DISK - - - ON - - - USE - - - DISTINCT - - - OPEN - - - USER - - - DISTRIBUTED - - - OPENDATASOURCE - - - VALUES - - - DOUBLE - - - OPENQUERY - - - VARYING - - - DROP - - - OPENROWSET - - - VIEW - - - DUMMY - - - OPENXML - - - WAITFOR - - - DUMP - - - OPTION - - - WHEN - - - ELSE - - - OR - - - WHERE - - - END - - - ORDER - - - WHILE - - - ERRLVL - - - OUTER - - - WITH - - - ESCAPE - - - OVER - - - WRITETEXT - - - ABSOLUTE - - - FOUND - - - PRESERVE - - - ACTION - - - FREE - - - PRIOR - - - ADMIN - - - GENERAL - - - PRIVILEGES - - - AFTER - - - GET - - - READS - - - AGGREGATE - - - GLOBAL - - - REAL - - - ALIAS - - - GO - - - RECURSIVE - - - ALLOCATE - - - GROUPING - - - REF - - - ARE - - - HOST - - - REFERENCING - - - ARRAY - - - HOUR - - - RELATIVE - - - ASSERTION - - - IGNORE - - - RESULT - - - AT - - - IMMEDIATE - - - RETURNS - - - BEFORE - - - INDICATOR - - - ROLE - - - BINARY - - - INITIALIZE - - - ROLLUP - - - BIT - - - INITIALLY - - - ROUTINE - - - BLOB - - - INOUT - - - ROW - - - BOOLEAN - - - INPUT - - - ROWS - - - BOTH - - - INT - - - SAVEPOINT - - - BREADTH - - - INTEGER - - - SCROLL - - - CALL - - - INTERVAL - - - SCOPE - - - CASCADED - - - ISOLATION - - - SEARCH - - - CAST - - - ITERATE - - - SECOND - - - CATALOG - - - LANGUAGE - - - SECTION - - - CHAR - - - LARGE - - - SEQUENCE - - - CHARACTER - - - LAST - - - SESSION - - - CLASS - - - LATERAL - - - SETS - - - CLOB - - - LEADING - - - SIZE - - - COLLATION - - - LESS - - - SMALLINT - - - COMPLETION - - - LEVEL - - - SPACE - - - CONNECT - - - LIMIT - - - SPECIFIC - - - CONNECTION - - - LOCAL - - - SPECIFICTYPE - - - CONSTRAINTS - - - LOCALTIME - - - SQL - - - CONSTRUCTOR - - - LOCALTIMESTAMP - - - SQLEXCEPTION - - - CORRESPONDING - - - LOCATOR - - - SQLSTATE - - - CUBE - - - MAP - - - SQLWARNING - - - CURRENT_PATH - - - MATCH - - - START - - - CURRENT_ROLE - - - MINUTE - - - STATE - - - CYCLE - - - MODIFIES - - - STATEMENT - - - DATA - - - MODIFY - - - STATIC - - - DATE - - - MODULE - - - STRUCTURE - - - DAY - - - MONTH - - - TEMPORARY - - - DEC - - - NAMES - - - TERMINATE - - - DECIMAL - - - NATURAL - - - THAN - - - DEFERRABLE - - - NCHAR - - - TIME - - - DEFERRED - - - NCLOB - - - TIMESTAMP - - - DEPTH - - - NEW - - - TIMEZONE_HOUR - - - DEREF - - - NEXT - - - TIMEZONE_MINUTE - - - DESCRIBE - - - NO - - - TRAILING - - - DESCRIPTOR - - - NONE - - - TRANSLATION - - - DESTROY - - - NUMERIC - - - TREAT - - - DESTRUCTOR - - - OBJECT - - - TRUE - - - DETERMINISTIC - - - OLD - - - UNDER - - - DICTIONARY - - - ONLY - - - UNKNOWN - - - DIAGNOSTICS - - - OPERATION - - - UNNEST - - - DISCONNECT - - - ORDINALITY - - - USAGE - - - DOMAIN - - - OUT - - - USING - - - DYNAMIC - - - OUTPUT - - - VALUE - - - EACH - - - PAD - - - VARCHAR - - - END-EXEC - - - PARAMETER - - - VARIABLE - - - EQUALS - - - PARAMETERS - - - WHENEVER - - - EVERY - - - PARTIAL - - - WITHOUT - - - EXCEPTION - - - PATH - - - WORK - - - EXTERNAL - - - POSTFIX - - - WRITE - - - FALSE - - - PREFIX - - - YEAR - - - FIRST - - - PREORDER - - - ZONE - - - FLOAT - - - PREPARE - - - ADA - - - AVG - - - BIT_LENGTH - - - CHAR_LENGTH - - - CHARACTER_LENGTH - - - COUNT - - - EXTRACT - - - FORTRAN - - - INCLUDE - - - INSENSITIVE - - - LOWER - - - MAX - - - MIN - - - OCTET_LENGTH - - - OVERLAPS - - - PASCAL - - - POSITION - - - SQLCA - - - SQLCODE - - - SQLERROR - - - SUBSTRING - - - SUM - - - TRANSLATE - - - TRIM - - - UPPER - - diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs index 9ad6c68967..636c39c932 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.cs @@ -6,7 +6,6 @@ // New attributes that are designed to work with Microsoft.Data.SqlClient and are publicly documented should be included in future. [assembly: System.CLSCompliant(true)] -[assembly: System.Resources.NeutralResourcesLanguageAttribute("en-US")] namespace Microsoft.Data { /// @@ -24,6 +23,8 @@ public static class SqlDbTypeExtensions { /// public const System.Data.SqlDbType Json = (System.Data.SqlDbType)35; + /// + public const System.Data.SqlDbType Vector = (System.Data.SqlDbType)36; } } @@ -1039,6 +1040,7 @@ public SqlConnectionStringBuilder(string connectionString) { } /// [System.ComponentModel.DisplayNameAttribute("Data Source")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] + [System.ComponentModel.TypeConverter("Microsoft.Data.SqlClient.SqlConnectionStringBuilder+SqlDataSourceConverter, Microsoft.Data.SqlClient")] public string DataSource { get { throw null; } set { } } /// [System.ComponentModel.DisplayNameAttribute("Enclave Attestation Url")] @@ -1072,6 +1074,7 @@ public SqlConnectionStringBuilder(string connectionString) { } /// [System.ComponentModel.DisplayNameAttribute("Failover Partner")] [System.ComponentModel.RefreshPropertiesAttribute(System.ComponentModel.RefreshProperties.All)] + [System.ComponentModel.TypeConverter("Microsoft.Data.SqlClient.SqlConnectionStringBuilder+SqlDataSourceConverter, Microsoft.Data.SqlClient")] public string FailoverPartner { get { throw null; } set { } } /// [System.ComponentModel.DisplayNameAttribute("Failover Partner SPN")] @@ -1255,7 +1258,7 @@ protected override void OnRowUpdating(System.Data.Common.RowUpdatingEventArgs va protected override void TerminateBatching() { } } /// - public partial class SqlDataReader : System.Data.Common.DbDataReader, System.Data.IDataReader, System.IDisposable + public partial class SqlDataReader : System.Data.Common.DbDataReader, System.Data.IDataReader, System.Data.Common.IDbColumnSchemaGenerator, System.IDisposable { internal SqlDataReader() { } /// @@ -1291,6 +1294,8 @@ public override void Close() { } public override char GetChar(int i) { throw null; } /// public override long GetChars(int i, long dataIndex, char[] buffer, int bufferIndex, int length) { throw null; } + /// + public System.Collections.ObjectModel.ReadOnlyCollection GetColumnSchema() { throw null; } /// public override string GetDataTypeName(int i) { throw null; } /// @@ -1353,6 +1358,8 @@ public override void Close() { } public virtual System.Data.SqlTypes.SqlInt32 GetSqlInt32(int i) { throw null; } /// public virtual System.Data.SqlTypes.SqlInt64 GetSqlInt64(int i) { throw null; } + /// + virtual public Microsoft.Data.SqlTypes.SqlJson GetSqlJson(int i) { throw null; } /// public virtual System.Data.SqlTypes.SqlMoney GetSqlMoney(int i) { throw null; } /// @@ -1363,6 +1370,8 @@ public override void Close() { } public virtual object GetSqlValue(int i) { throw null; } /// public virtual int GetSqlValues(object[] values) { throw null; } + /// + public virtual Microsoft.Data.SqlTypes.SqlVector GetSqlVector(int i) where T : unmanaged { throw null; } /// public virtual System.Data.SqlTypes.SqlXml GetSqlXml(int i) { throw null; } /// @@ -2403,7 +2412,25 @@ public SqlJson(System.Text.Json.JsonDocument jsonDoc) { } public static SqlJson Null => throw null; /// public string Value { get { throw null; } } - /// - virtual public SqlJson GetSqlJson(int i) { throw null; } + /// + public override string ToString() { throw null; } + } + + /// + public readonly struct SqlVector : System.Data.SqlTypes.INullable + where T : unmanaged + { + /// + public SqlVector(System.ReadOnlyMemory memory) { } + /// + public bool IsNull => throw null; + /// + public static SqlVector? Null => throw null; + /// + public int Length { get { throw null; } } + /// + public System.ReadOnlyMemory Memory { get { throw null; } } + /// + public static SqlVector CreateNull(int length) { throw null; } } } diff --git a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj index c7e42bd800..445fcca7a7 100644 --- a/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/ref/Microsoft.Data.SqlClient.csproj @@ -2,13 +2,13 @@ false net462 - $(ObjFolder)$(Configuration)\$(AssemblyName)\ref\ + $(ObjFolder)$(Configuration)\$(AssemblyName)\ref\ $(BinFolder)$(Configuration)\$(AssemblyName)\ref\ $(OutputPath)\Microsoft.Data.SqlClient.xml Framework $(BaseProduct) - Debug;Release; + Debug;Release - + true $(SigningKeyPath) @@ -32,17 +32,21 @@ - - + + + - $(MicrosoftDataSqlClientSniVersion) All runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Common/Microsoft/Data/Common/NameValuePermission.cs b/src/Microsoft.Data.SqlClient/netfx/src/Common/Microsoft/Data/Common/NameValuePermission.cs deleted file mode 100644 index a7ad9be288..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Common/Microsoft/Data/Common/NameValuePermission.cs +++ /dev/null @@ -1,293 +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. - -namespace Microsoft.Data.Common -{ - - using System; - using System.Collections; - using System.Diagnostics; - - [Serializable] // MDAC 83147 - sealed internal class NameValuePermission : IComparable - { - // reused as both key and value nodes - // key nodes link to value nodes - // value nodes link to key nodes - private string _value; - - // value node with (_restrictions != null) are allowed to match connection strings - private DBConnectionString _entry; - - private NameValuePermission[] _tree; // with branches - - static internal readonly NameValuePermission Default = null;// = new NameValuePermission(String.Empty, new string[] { "File Name" }, KeyRestrictionBehavior.AllowOnly); - - internal NameValuePermission() - { // root node - } - - private NameValuePermission(string keyword) - { - _value = keyword; - } - - private NameValuePermission(string value, DBConnectionString entry) - { - _value = value; - _entry = entry; - } - - private NameValuePermission(NameValuePermission permit) - { // deep-copy - _value = permit._value; - _entry = permit._entry; - _tree = permit._tree; - if (_tree != null) - { - NameValuePermission[] tree = (_tree.Clone() as NameValuePermission[]); - for (int i = 0; i < tree.Length; ++i) - { - if (tree[i] != null) - { // WebData 98488 - tree[i] = tree[i].CopyNameValue(); // deep copy - } - } - _tree = tree; - } - } - - int IComparable.CompareTo(object a) - { - return StringComparer.Ordinal.Compare(_value, ((NameValuePermission)a)._value); - } - - static internal void AddEntry(NameValuePermission kvtree, ArrayList entries, DBConnectionString entry) - { - Debug.Assert(entry != null, "null DBConnectionString"); - - if (entry.KeyChain != null) - { - for (NameValuePair keychain = entry.KeyChain; keychain != null; keychain = keychain.Next) - { - NameValuePermission kv; - - kv = kvtree.CheckKeyForValue(keychain.Name); - if (kv == null) - { - kv = new NameValuePermission(keychain.Name); - kvtree.Add(kv); // add directly into live tree - } - kvtree = kv; - - kv = kvtree.CheckKeyForValue(keychain.Value); - if (kv == null) - { - DBConnectionString insertValue = keychain.Next != null ? null : entry; - kv = new NameValuePermission(keychain.Value, insertValue); - kvtree.Add(kv); // add directly into live tree - if (insertValue != null) - { - entries.Add(insertValue); - } - } - else if (keychain.Next == null) - { // shorter chain potential - if (kv._entry != null) - { - Debug.Assert(entries.Contains(kv._entry), "entries doesn't contain entry"); - entries.Remove(kv._entry); - kv._entry = kv._entry.Intersect(entry); // union new restrictions into existing tree - } - else - { - kv._entry = entry; - } - entries.Add(kv._entry); - } - kvtree = kv; - } - } - else - { // global restrictions, MDAC 84443 - DBConnectionString kentry = kvtree._entry; - if (kentry != null) - { - Debug.Assert(entries.Contains(kentry), "entries doesn't contain entry"); - entries.Remove(kentry); - kvtree._entry = kentry.Intersect(entry); - } - else - { - kvtree._entry = entry; - } - entries.Add(kvtree._entry); - } - } - - internal void Intersect(ArrayList entries, NameValuePermission target) - { - if (target == null) - { - _tree = null; - _entry = null; - } - else - { - if (_entry != null) - { - entries.Remove(_entry); - _entry = _entry.Intersect(target._entry); - entries.Add(_entry); - } - else if (target._entry != null) - { - _entry = target._entry.Intersect(null); - entries.Add(_entry); - } - - if (_tree != null) - { - int count = _tree.Length; - for (int i = 0; i < _tree.Length; ++i) - { - NameValuePermission kvtree = target.CheckKeyForValue(_tree[i]._value); - if (kvtree != null) - { // does target tree contain our value - _tree[i].Intersect(entries, kvtree); - } - else - { - _tree[i] = null; - --count; - } - } - if (0 == count) - { - _tree = null; - } - else if (count < _tree.Length) - { - NameValuePermission[] kvtree = new NameValuePermission[count]; - for (int i = 0, j = 0; i < _tree.Length; ++i) - { - if (_tree[i] != null) - { - kvtree[j++] = _tree[i]; - } - } - _tree = kvtree; - } - } - } - } - - private void Add(NameValuePermission permit) - { - NameValuePermission[] tree = _tree; - int length = tree != null ? tree.Length : 0; - NameValuePermission[] newtree = new NameValuePermission[1 + length]; - for (int i = 0; i < newtree.Length - 1; ++i) - { - newtree[i] = tree[i]; - } - newtree[length] = permit; - Array.Sort(newtree); - _tree = newtree; - } - - internal bool CheckValueForKeyPermit(DBConnectionString parsetable) - { - if (parsetable == null) - { - return false; - } - bool hasMatch = false; - NameValuePermission[] keytree = _tree; // _tree won't mutate but Add will replace it - if (keytree != null) - { - hasMatch = parsetable.IsEmpty; // MDAC 86773 - if (!hasMatch) - { - - // which key do we follow the key-value chain on - for (int i = 0; i < keytree.Length; ++i) - { - NameValuePermission permitKey = keytree[i]; - if (permitKey != null) - { - string keyword = permitKey._value; -#if DEBUG - Debug.Assert(permitKey._entry == null, "key member has no restrictions"); -#endif - if (parsetable.ContainsKey(keyword)) - { - string valueInQuestion = (string)parsetable[keyword]; - - // keyword is restricted to certain values - NameValuePermission permitValue = permitKey.CheckKeyForValue(valueInQuestion); - if (permitValue != null) - { - //value does match - continue the chain down that branch - if (permitValue.CheckValueForKeyPermit(parsetable)) - { - hasMatch = true; - // adding a break statement is tempting, but wrong - // user can safely extend their restrictions for current rule to include missing keyword - // i.e. Add("provider=sqloledb;integrated security=sspi", "data provider=", KeyRestrictionBehavior.AllowOnly); - // i.e. Add("data provider=msdatashape;provider=sqloledb;integrated security=sspi", "", KeyRestrictionBehavior.AllowOnly); - } - else - { // failed branch checking - return false; - } - } - else - { // value doesn't match to expected values - fail here - return false; - } - } - } - // else try next keyword - } - } - // partial chain match, either leaf-node by shorter chain or fail mid-chain if ( _restrictions == null) - } - - DBConnectionString entry = _entry; - if (entry != null) - { - // also checking !hasMatch is tempting, but wrong - // user can safely extend their restrictions for current rule to include missing keyword - // i.e. Add("provider=sqloledb;integrated security=sspi", "data provider=", KeyRestrictionBehavior.AllowOnly); - // i.e. Add("provider=sqloledb;", "integrated security=;", KeyRestrictionBehavior.AllowOnly); - hasMatch = entry.IsSupersetOf(parsetable); - } - - return hasMatch; // mid-chain failure - } - - private NameValuePermission CheckKeyForValue(string keyInQuestion) - { - NameValuePermission[] valuetree = _tree; // _tree won't mutate but Add will replace it - if (valuetree != null) - { - for (int i = 0; i < valuetree.Length; ++i) - { - NameValuePermission permitValue = valuetree[i]; - if (String.Equals(keyInQuestion, permitValue._value, StringComparison.OrdinalIgnoreCase)) - { - return permitValue; - } - } - } - return null; - } - - internal NameValuePermission CopyNameValue() - { - return new NameValuePermission(this); - } - } -} 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 de61ff0a54..172f4c3897 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -1,4 +1,4 @@ - + {407890AC-9876-4FEF-A6F1-F36A876BAADE} @@ -12,11 +12,8 @@ $(OutputPath)\Microsoft.Data.SqlClient.xml $(ObjPath)$(AssemblyName)\netfx\ Framework $(BaseProduct) - - True false - $(DefineConstants);NETFRAMEWORK; + false @@ -34,6 +31,11 @@ $([System.IO.Path]::Combine('$(IntermediateOutputPath)\$(TargetFramework)','$(GeneratedSourceFileName)')) + + + <_Parameter1>UnitTests + + @@ -56,7 +58,10 @@ True True None - MinimumRecommendedRules.ruleset + + + MinimumRecommendedRules.ruleset + True True $(DefineConstants);USEOFFSET;CODE_ANALYSIS_BASELINE;FEATURE_LEGACYSURFACEAREA;FEATURE_UTF32;FEATURE_UTF7;TRACE; @@ -172,6 +177,9 @@ Interop\Windows\Sni\IoType.cs + + Interop\Windows\Sni\ISniNativeMethods.cs + Interop\Windows\Sni\Prefix.cs @@ -193,6 +201,21 @@ Interop\Windows\Sni\SniError.cs + + Interop\Windows\Sni\SniNativeMethodsArm64.netfx.cs + + + Interop\Windows\Sni\SniNativeMethodsNotSupported.netfx.cs + + + Interop\Windows\Sni\SniNativeMethodsX64.netfx.cs + + + Interop\Windows\Sni\SniNativeMethodsX86.netfx.cs + + + Interop\Windows\Sni\SniNativeWrapper.cs + Interop\Windows\Sni\SqlDependencyProcessDispatcherStorage.netfx.cs @@ -217,14 +240,35 @@ Microsoft\Data\Common\BitConverterCompatible.cs - - Microsoft\Data\Common\DbConnectionStringCommon.cs + + Microsoft\Data\Common\ConnectionString\AttestationProtocolUtilities.cs + + + Microsoft\Data\Common\ConnectionString\DbConnectionOptions.cs + + + Microsoft\Data\Common\ConnectionString\DbConnectionOptions.Debug.cs + + + Microsoft\Data\Common\ConnectionString\DbConnectionStringDefaults.cs + + + Microsoft\Data\Common\ConnectionString\DbConnectionStringKeywords.cs - - Microsoft\Data\Common\DbConnectionOptions.Common.cs + + Microsoft\Data\Common\ConnectionString\DbConnectionStringSynonyms.cs - - Microsoft\Data\Common\DbConnectionPoolKey.cs + + Microsoft\Data\Common\ConnectionString\DbConnectionString.netfx.cs + + + Microsoft\Data\Common\ConnectionString\DbConnectionStringUtilities.cs + + + Microsoft\Data\Common\ConnectionString\IpAddressPreferenceUtilities.cs + + + Microsoft\Data\Common\ConnectionString\PoolBlockingUtilities.cs Microsoft\Data\Common\MultipartIdentifier.cs @@ -247,60 +291,69 @@ Microsoft\Data\ProviderBase\DbConnectionInternal.cs - - Microsoft\Data\ProviderBase\DbConnectionPool.cs + + Microsoft\Data\SqlClient\ConnectionPool\ChannelDbConnectionPool.cs + + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolAuthenticationContext.cs + + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolAuthenticationContextKey.cs + + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolGroup.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolAuthenticationContext.cs + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolGroupProviderInfo.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolAuthenticationContextKey.cs + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolIdentity.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolCounters.netfx.cs + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolIdentity.Windows.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolGroup.cs + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolKey.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolGroupProviderInfo.cs + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolOptions.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolIdentity.cs + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolProviderInfo.cs + + + Microsoft\Data\SqlClient\ConnectionPool\DbConnectionPoolState.cs + + + Microsoft\Data\SqlClient\ConnectionPool\IDbConnectionPool.cs + + + Microsoft\Data\SqlClient\ConnectionPool\SqlConnectionPoolGroupProviderInfo.cs + + + Microsoft\Data\SqlClient\SqlConnectionPoolKey.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolIdentity.Windows.cs + + Microsoft\Data\SqlClient\ConnectionPool\SqlConnectionPoolProviderInfo.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolOptions.cs + + Microsoft\Data\SqlClient\ConnectionPool\WaitHandleDbConnectionPool.cs - - Microsoft\Data\ProviderBase\DbConnectionPoolProviderInfo.cs + + Microsoft\Data\SqlClient\Diagnostics\SqlClientMetrics.cs Microsoft\Data\ProviderBase\DbMetaDataFactory.cs + + Microsoft\Data\ProviderBase\DbReferenceCollection.cs + Microsoft\Data\ProviderBase\FieldNameLookup.cs Microsoft\Data\ProviderBase\TimeoutTimer.cs - - Microsoft\Data\SqlClient\SSPI\NativeSSPIContextProvider.cs - - - Microsoft\Data\SqlClient\SSPI\NegotiateSSPIContextProvider.cs - - - Microsoft\Data\SqlClient\SSPI\SSPIContextProvider.cs - - - Microsoft\Data\SqlClient\TdsParser.cs - - - Microsoft\Data\Sql\SqlDataSourceEnumerator.cs - Microsoft\Data\Sql\SqlDataSourceEnumerator.Windows.cs @@ -313,6 +366,27 @@ Microsoft\Data\Sql\SqlNotificationRequest.cs + + Resources\ResCategoryAttribute.cs + + + Microsoft\Data\SqlClient\SSPI\NativeSspiContextProvider.cs + + + Microsoft\Data\SqlClient\SSPI\NegotiateSspiContextProvider.cs + + + Microsoft\Data\SqlClient\SSPI\SspiContextProvider.cs + + + Microsoft\Data\SqlClient\SSPI\SspiAuthenticationParameters.cs + + + Microsoft\Data\SqlClient\TdsParser.cs + + + Microsoft\Data\Sql\SqlDataSourceEnumerator.cs + Microsoft\Data\SqlClient\AAsyncCallContext.cs @@ -364,6 +438,18 @@ Microsoft\Data\SqlClient\EnclaveSessionCache.cs + + Microsoft\Data\SqlClient\LocalDb\LocalDbApi.Windows.cs + + + Microsoft\Data\SqlClient\LocalDb\LocalDbConfigurationSection.netfx.cs + + + Microsoft\Data\SqlClient\LocalDb\LocalDbInstanceElement.netfx.cs + + + Microsoft\Data\SqlClient\LocalDb\LocalDbInstancesCollection.netfx.cs + Microsoft\Data\SqlClient\LocalAppContextSwitches.cs @@ -373,6 +459,9 @@ Microsoft\Data\SqlClient\OnChangedEventHandler.cs + + Microsoft\Data\SqlClient\PacketHandle.Windows.cs + Microsoft\Data\SqlClient\ParameterPeekAheadValue.cs @@ -382,6 +471,15 @@ Microsoft\Data\SqlClient\Reliability\AppConfigManager.cs + + Microsoft\Data\SqlClient\Reliability\IAppContextSwitchOverridesSection.cs + + + Microsoft\Data\SqlClient\Reliability\ISqlConfigurableRetryCommandSection.cs + + + Microsoft\Data\SqlClient\Reliability\ISqlConfigurableRetryConnectionSection.cs + Microsoft\Data\SqlClient\Reliability\SqlRetryingEventArgs.cs @@ -421,15 +519,9 @@ Microsoft\Data\SqlClient\Server\ExtendedClrTypeCode.cs - - Microsoft\Data\SqlClient\Server\ITypedGetters.cs - Microsoft\Data\SqlClient\Server\ITypedGettersV3.cs - - Microsoft\Data\SqlClient\Server\ITypedSetters.cs - Microsoft\Data\SqlClient\Server\ITypedSettersV3.cs @@ -439,15 +531,6 @@ Microsoft\Data\SqlClient\Server\MetadataUtilsSmi.cs - - Microsoft\Data\SqlClient\Server\SmiEventSink.cs - - - Microsoft\Data\SqlClient\Server\SmiEventSink_Default.Common.cs - - - Microsoft\Data\SqlClient\Server\SmiEventSink_Default.netfx.cs - Microsoft\Data\SqlClient\Server\SmiGettersStream.cs @@ -466,8 +549,8 @@ Microsoft\Data\SqlClient\Server\SmiTypedGetterSetter.cs - - Microsoft\Data\SqlClient\Server\SmiXetterAccess.Common.cs + + Microsoft\Data\SqlClient\Server\SmiXetterAccess.cs Microsoft\Data\SqlClient\Server\SmiXetterTypeCode.cs @@ -487,18 +570,21 @@ Microsoft\Data\SqlClient\Server\SqlRecordBuffer.cs - - Microsoft\Data\SqlClient\SqlTransaction.Common.cs + + Microsoft\Data\SqlClient\Server\TriggerAction.netfx.cs Microsoft\Data\SqlClient\Server\ValueUtilsSmi.cs - - Microsoft\Data\SqlClient\Server\ValueUtilsSmi.netfx.cs - Microsoft\Data\SqlClient\Server\SqlSer.cs + + Microsoft\Data\SqlClient\ISqlVector.cs + + + Microsoft\Data\SqlClient\SessionHandle.Windows.cs + Microsoft\Data\SqlClient\SignatureVerificationCache.cs @@ -526,6 +612,9 @@ Microsoft\Data\SqlClient\SqlBuffer.cs + + Microsoft\Data\SqlClient\SqlBulkCopy.cs + Microsoft\Data\SqlClient\SqlAuthenticationToken.cs @@ -574,6 +663,12 @@ Microsoft\Data\SqlClient\SqlClientMetaDataCollectionNames.cs + + Microsoft\Data\SqlClient\SqlClientPermission.netfx.cs + + + Microsoft\Data\SqlClient\SqlClientPermissionAttribute.netfx.cs + Microsoft\Data\SqlClient\SqlClientSymmetricKey.cs @@ -607,14 +702,8 @@ Microsoft\Data\SqlClient\SqlConnectionEncryptOptionConverter.cs - - Microsoft\Data\SqlClient\SqlConnectionPoolGroupProviderInfo.cs - - - Microsoft\Data\SqlClient\SqlConnectionPoolKey.cs - - - Microsoft\Data\SqlClient\SqlConnectionPoolProviderInfo.cs + + Microsoft\Data\SqlClient\SqlConnectionFactory.cs Microsoft\Data\SqlClient\SqlConnectionString.cs @@ -631,6 +720,12 @@ Microsoft\Data\SqlClient\SqlDataAdapter.cs + + Microsoft\Data\SqlClient\SqlDataReader.cs + + + Microsoft\Data\SqlClient\SqlDbColumn.cs + Microsoft\Data\SqlClient\SqlDelegatedTransaction.cs @@ -736,6 +831,9 @@ Microsoft\Data\SqlClient\SqlSymmetricKeyCache.cs + + Microsoft\Data\SqlClient\SqlTransaction.cs + Microsoft\Data\SqlClient\SqlUdtInfo.cs @@ -754,6 +852,9 @@ Microsoft\Data\SqlClient\TdsParserStateObject.cs + + Microsoft\Data\SqlClient\TdsParserStateObjectFactory.Windows.cs + Microsoft\Data\SqlClient\TdsParserStaticMethods.cs @@ -769,12 +870,24 @@ Microsoft\Data\SqlClient\TdsValueSetter.cs + + Microsoft\Data\SqlClient\Utilities\BufferWriterExtensions.netfx.cs + + + Microsoft\Data\SqlClient\Utilities\ObjectPool.cs + + + Microsoft\Data\SqlClient\Utilities\ObjectPools.cs + Microsoft\Data\SqlClient\VirtualSecureModeEnclaveProvider.cs Microsoft\Data\SqlClient\VirtualSecureModeEnclaveProviderBase.cs + + Microsoft\Data\SqlDbTypeExtensions.cs + Microsoft\Data\SqlTypes\SqlFileStream.cs @@ -787,65 +900,30 @@ Microsoft\Data\SqlTypes\SqlJson.cs - - Resources\ResCategoryAttribute.cs + + Microsoft\Data\SqlTypes\SqlVector.cs Resources\ResDescriptionAttribute.cs - - Microsoft\Data\ProviderBase\DbReferenceCollection.cs - - - Microsoft\Data\SqlDbTypeExtensions.cs + + System\Buffers\ArrayBufferWriter.netfx.cs System\IO\StreamExtensions.netfx.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + System\Runtime\CompilerServices\IsExternalInit.netfx.cs + + - - - - - - - + - + @@ -870,37 +948,27 @@ Resources\%(RecursiveDir)%(Filename)%(Extension) Microsoft.Data.SqlClient.Resources.%(Filename).resources - + Microsoft.Data.SqlClient.SqlMetaData.xml + Resources\Microsoft.Data.SqlClient.SqlMetaData.xml PreserveNewest - - {5477469E-83B1-11D2-8B49-00A0C9B7C9C4} - 2 - 4 - 0 - tlbimp - False - True - - - - - + + + - $(MicrosoftDataSqlClientSniVersion) All runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionOptions.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionOptions.cs deleted file mode 100644 index e240c3d822..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DbConnectionOptions.cs +++ /dev/null @@ -1,224 +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.Runtime.Versioning; -using System.Security; -using System.Text; - -namespace Microsoft.Data.Common -{ - internal partial class DbConnectionOptions - { - public string this[string keyword] => _parsetable[keyword]; - - private PermissionSet _permissionset; - - protected internal virtual PermissionSet CreatePermissionSet() => null; - - internal void DemandPermission() - { - if (_permissionset is null) - { - _permissionset = CreatePermissionSet(); - } - _permissionset.Demand(); - } - - internal bool HasBlankPassword - { - get - { - if (!ConvertValueToIntegratedSecurity()) - { - if (_parsetable.ContainsKey(KEY.Password)) - { - return ADP.IsEmpty(_parsetable[KEY.Password]); - } - else - if (_parsetable.ContainsKey(SYNONYM.Pwd)) - { - return ADP.IsEmpty(_parsetable[SYNONYM.Pwd]); // MDAC 83097 - } - else - { - return (_parsetable.ContainsKey(KEY.User_ID) && !ADP.IsEmpty(_parsetable[KEY.User_ID])) || - (_parsetable.ContainsKey(SYNONYM.UID) && !ADP.IsEmpty(_parsetable[SYNONYM.UID])); - } - } - return false; - } - } - - internal string ExpandKeyword(string keyword, string replacementValue) - { - // preserve duplicates, updated keyword value with replacement value - // if keyword not specified, append to end of the string - bool expanded = false; - int copyPosition = 0; - - StringBuilder builder = new(_usersConnectionString.Length); - for (NameValuePair current = _keyChain; current != null; current = current.Next) - { - if ((current.Name == keyword) && (current.Value == this[keyword])) - { - // only replace the parse end-result value instead of all values - // so that when duplicate-keywords occur other original values remain in place - AppendKeyValuePairBuilder(builder, current.Name, replacementValue); - builder.Append(';'); - expanded = true; - } - else - { - builder.Append(_usersConnectionString, copyPosition, current.Length); - } - copyPosition += current.Length; - } - - if (!expanded) - { - AppendKeyValuePairBuilder(builder, keyword, replacementValue); - } - return builder.ToString(); - } - - internal static void AppendKeyValuePairBuilder(StringBuilder builder, string keyName, string keyValue, bool useOdbcRules = false) - { - ADP.CheckArgumentNull(builder, nameof(builder)); - ADP.CheckArgumentLength(keyName, nameof(keyName)); - - if (keyName == null || !s_connectionStringValidKeyRegex.IsMatch(keyName)) - { - throw ADP.InvalidKeyname(keyName); - } - if (keyValue != null && !IsValueValidInternal(keyValue)) - { - throw ADP.InvalidValue(keyName); - } - - if ((0 < builder.Length) && (';' != builder[builder.Length - 1])) - { - builder.Append(";"); - } - - if (useOdbcRules) - { - builder.Append(keyName); - } - else - { - builder.Append(keyName.Replace("=", "==")); - } - builder.Append("="); - - if (keyValue != null) - { // else =; - if (useOdbcRules) - { - if ((0 < keyValue.Length) && - (('{' == keyValue[0]) || (0 <= keyValue.IndexOf(';')) || (0 == string.Compare(DbConnectionStringKeywords.Driver, keyName, StringComparison.OrdinalIgnoreCase))) && - !s_connectionStringQuoteOdbcValueRegex.IsMatch(keyValue)) - { - // always quote Driver value (required for ODBC Version 2.65 and earlier) - // always quote values that contain a ';' - builder.Append('{').Append(keyValue.Replace("}", "}}")).Append('}'); - } - else - { - builder.Append(keyValue); - } - } - else if (s_connectionStringQuoteValueRegex.IsMatch(keyValue)) - { - // -> - builder.Append(keyValue); - } - else if ((-1 != keyValue.IndexOf('\"')) && (-1 == keyValue.IndexOf('\''))) - { - // -> <'val"ue'> - builder.Append('\''); - builder.Append(keyValue); - builder.Append('\''); - } - else - { - // -> <"val'ue"> - // <=value> -> <"=value"> - // <;value> -> <";value"> - // < value> -> <" value"> - // -> <"va lue"> - // -> <"va'""lue"> - builder.Append('\"'); - builder.Append(keyValue.Replace("\"", "\"\"")); - builder.Append('\"'); - } - } - } - - // SxS notes: - // * this method queries "DataDirectory" value from the current AppDomain. - // This string is used for to replace "!DataDirectory!" values in the connection string, it is not considered as an "exposed resource". - // * This method uses GetFullPath to validate that root path is valid, the result is not exposed out. - [ResourceExposure(ResourceScope.None)] - [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] - internal static string ExpandDataDirectory(string keyword, string value, ref string datadir) - { - string fullPath = null; - if (value != null && value.StartsWith(DataDirectory, StringComparison.OrdinalIgnoreCase)) - { - - string rootFolderPath = datadir; - if (rootFolderPath == null) - { - // find the replacement path - object rootFolderObject = AppDomain.CurrentDomain.GetData("DataDirectory"); - rootFolderPath = (rootFolderObject as string); - if (rootFolderObject != null && rootFolderPath == null) - { - throw ADP.InvalidDataDirectory(); - } - else if (ADP.IsEmpty(rootFolderPath)) - { - rootFolderPath = AppDomain.CurrentDomain.BaseDirectory; - } - if (rootFolderPath == null) - { - rootFolderPath = ""; - } - // cache the |DataDir| for ExpandDataDirectories - datadir = rootFolderPath; - } - - // We don't know if rootFolderpath ends with '\', and we don't know if the given name starts with onw - int fileNamePosition = DataDirectory.Length; // filename starts right after the '|datadirectory|' keyword - bool rootFolderEndsWith = (0 < rootFolderPath.Length) && rootFolderPath[rootFolderPath.Length - 1] == '\\'; - bool fileNameStartsWith = (fileNamePosition < value.Length) && value[fileNamePosition] == '\\'; - - // replace |datadirectory| with root folder path - if (!rootFolderEndsWith && !fileNameStartsWith) - { - // need to insert '\' - fullPath = rootFolderPath + '\\' + value.Substring(fileNamePosition); - } - else if (rootFolderEndsWith && fileNameStartsWith) - { - // need to strip one out - fullPath = rootFolderPath + value.Substring(fileNamePosition + 1); - } - else - { - // simply concatenate the strings - fullPath = rootFolderPath + value.Substring(fileNamePosition); - } - - // verify root folder path is a real path without unexpected "..\" - if (!ADP.GetFullPath(fullPath).StartsWith(rootFolderPath, StringComparison.Ordinal)) - { - throw ADP.InvalidConnectionOptionValue(keyword); - } - } - return fullPath; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/GreenMethods.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/GreenMethods.cs deleted file mode 100644 index 43099a0bff..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/GreenMethods.cs +++ /dev/null @@ -1,57 +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.Reflection; -using Microsoft.Data.SqlClient; - -namespace Microsoft.Data.Common -{ - internal static class GreenMethods - { - - private const string ExtensionAssemblyRef = "System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=" + AssemblyRef.EcmaPublicKey; - - // For performance, we should convert these calls to using DynamicMethod with a Delegate, or - // even better, friend assemblies if its possible; so far there's only one of these per - // AppDomain, so we're OK. - - //------------------------------------------------------------------------------ - // Access to the DbProviderServices type - private const string SystemDataCommonDbProviderServices_TypeName = "System.Data.Common.DbProviderServices, " + ExtensionAssemblyRef; - internal static Type SystemDataCommonDbProviderServices_Type = Type.GetType(SystemDataCommonDbProviderServices_TypeName, false); - - //------------------------------------------------------------------------------ - // Access to the SqlProviderServices class singleton instance; - private const string MicrosoftDataSqlClientSqlProviderServices_TypeName = "Microsoft.Data.SqlClient.SQLProviderServices, " + ExtensionAssemblyRef; - private static FieldInfo MicrosoftDataSqlClientSqlProviderServices_Instance_FieldInfo; - - internal static object MicrosoftDataSqlClientSqlProviderServices_Instance() - { - if (MicrosoftDataSqlClientSqlProviderServices_Instance_FieldInfo == null) - { - Type t = Type.GetType(MicrosoftDataSqlClientSqlProviderServices_TypeName, false); - - if (t != null) - { - MicrosoftDataSqlClientSqlProviderServices_Instance_FieldInfo = t.GetField("Instance", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static); - } - } - object result = MicrosoftDataSqlClientSqlProviderServices_Instance_GetValue(); - return result; - } - - [System.Security.Permissions.ReflectionPermission(System.Security.Permissions.SecurityAction.Assert, MemberAccess = true)] - private static object MicrosoftDataSqlClientSqlProviderServices_Instance_GetValue() - { - object result = null; - if (MicrosoftDataSqlClientSqlProviderServices_Instance_FieldInfo != null) - { - result = MicrosoftDataSqlClientSqlProviderServices_Instance_FieldInfo.GetValue(null); - } - return result; - } - - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperARM64.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperARM64.cs deleted file mode 100644 index 3c836ebeb1..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperARM64.cs +++ /dev/null @@ -1,141 +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.Runtime.InteropServices; -using System.Text; -using Interop.Windows.Sni; - -namespace Microsoft.Data.SqlClient -{ - internal static class SNINativeManagedWrapperARM64 - { - private const string SNI = "Microsoft.Data.SqlClient.SNI.arm64.dll"; - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIAddProviderWrapper")] - internal static extern uint SNIAddProvider(SNIHandle pConn, Provider ProvNum, [In] ref uint pInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIAddProviderWrapper")] - internal static extern uint SNIAddProviderWrapper(SNIHandle pConn, Provider ProvNum, [In] ref AuthProviderInfo pInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNICheckConnectionWrapper")] - internal static extern uint SNICheckConnection([In] SNIHandle pConn); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNICloseWrapper")] - internal static extern uint SNIClose(IntPtr pConn); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern void SNIGetLastError(out SniError pErrorStruct); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern void SNIPacketRelease(IntPtr pPacket); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIPacketResetWrapper")] - internal static extern void SNIPacketReset([In] SNIHandle pConn, IoType IOType, SNIPacket pPacket, ConsumerNumber ConsNum); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIQueryInfo(QueryType QType, ref uint pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIQueryInfo(QueryType QType, ref IntPtr pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIReadAsyncWrapper")] - internal static extern uint SNIReadAsync(SNIHandle pConn, ref IntPtr ppNewPacket); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIReadSyncOverAsync(SNIHandle pConn, ref IntPtr ppNewPacket, int timeout); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIRemoveProviderWrapper")] - internal static extern uint SNIRemoveProvider(SNIHandle pConn, Provider ProvNum); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNISecInitPackage(ref uint pcbMaxToken); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNISetInfoWrapper")] - internal static extern uint SNISetInfo(SNIHandle pConn, QueryType QType, [In] ref uint pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNITerminate(); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIWaitForSSLHandshakeToCompleteWrapper")] - internal static extern uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint UnmanagedIsTokenRestricted([In] IntPtr token, [MarshalAs(UnmanagedType.Bool)] out bool isRestricted); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint GetSniMaxComposedSpnLength(); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, out Guid pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, [MarshalAs(UnmanagedType.Bool)] out bool pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, out ushort portNum); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - internal static extern uint SNIGetPeerAddrStrWrapper([In] SNIHandle pConn, int bufferSize, StringBuilder addrBuffer, out uint addrLen); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, out Provider provNum); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIInitialize")] - internal static extern uint SNIInitialize([In] IntPtr pmo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIOpenSyncExWrapper(ref SniClientConsumerInfo pClientConsumerInfo, out IntPtr ppConn); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIOpenWrapper( - [In] ref SniConsumerInfo pConsumerInfo, - [MarshalAs(UnmanagedType.LPWStr)] string szConnect, - [In] SNIHandle pConn, - out IntPtr ppConn, - [MarshalAs(UnmanagedType.Bool)] bool fSync, - SqlConnectionIPAddressPreference ipPreference, - [In] ref SniDnsCacheInfo pDNSCachedInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr SNIPacketAllocateWrapper([In] SafeHandle pConn, IoType IOType); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIPacketGetDataWrapper([In] IntPtr packet, [In, Out] byte[] readBuffer, uint readBufferLength, out uint dataSize); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern unsafe void SNIPacketSetData(SNIPacket pPacket, [In] byte* pbBuf, uint cbBuf); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNISecGenClientContextWrapper")] - internal static extern unsafe uint SNISecGenClientContextWrapper( - [In] SNIHandle pConn, - [In, Out] byte* pIn, - uint cbIn, - [In, Out] byte[] pOut, - [In] ref uint pcbOut, - [MarshalAsAttribute(UnmanagedType.Bool)] out bool pfDone, - byte* szServerInfo, - uint cbServerInfo, - [MarshalAsAttribute(UnmanagedType.LPWStr)] string pwszUserName, - [MarshalAsAttribute(UnmanagedType.LPWStr)] string pwszPassword); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIWriteAsyncWrapper(SNIHandle pConn, [In] SNIPacket pPacket); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIWriteSyncOverAsync(SNIHandle pConn, [In] SNIPacket pPacket); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumOpenWrapper")] - internal static extern IntPtr SNIServerEnumOpen(); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumCloseWrapper")] - internal static extern void SNIServerEnumClose([In] IntPtr packet); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumReadWrapper", CharSet = CharSet.Unicode)] - internal static extern int SNIServerEnumRead([In] IntPtr packet, - [In, Out][MarshalAs(UnmanagedType.LPArray)] char[] readBuffer, - [In] int bufferLength, - [MarshalAs(UnmanagedType.Bool)] out bool more); - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs deleted file mode 100644 index 149f1d2f3a..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX64.cs +++ /dev/null @@ -1,142 +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.Runtime.InteropServices; -using System.Text; -using Interop.Windows.Sni; -using static Microsoft.Data.SqlClient.SNINativeMethodWrapper; - -namespace Microsoft.Data.SqlClient -{ - internal static class SNINativeManagedWrapperX64 - { - private const string SNI = "Microsoft.Data.SqlClient.SNI.x64.dll"; - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIAddProviderWrapper")] - internal static extern uint SNIAddProvider(SNIHandle pConn, Provider ProvNum, [In] ref uint pInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIAddProviderWrapper")] - internal static extern uint SNIAddProviderWrapper(SNIHandle pConn, Provider ProvNum, [In] ref AuthProviderInfo pInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNICheckConnectionWrapper")] - internal static extern uint SNICheckConnection([In] SNIHandle pConn); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNICloseWrapper")] - internal static extern uint SNIClose(IntPtr pConn); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern void SNIGetLastError(out SniError pErrorStruct); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern void SNIPacketRelease(IntPtr pPacket); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIPacketResetWrapper")] - internal static extern void SNIPacketReset([In] SNIHandle pConn, IoType IOType, SNIPacket pPacket, ConsumerNumber ConsNum); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIQueryInfo(QueryType QType, ref uint pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIQueryInfo(QueryType QType, ref IntPtr pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIReadAsyncWrapper")] - internal static extern uint SNIReadAsync(SNIHandle pConn, ref IntPtr ppNewPacket); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIReadSyncOverAsync(SNIHandle pConn, ref IntPtr ppNewPacket, int timeout); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIRemoveProviderWrapper")] - internal static extern uint SNIRemoveProvider(SNIHandle pConn, Provider ProvNum); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNISecInitPackage(ref uint pcbMaxToken); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNISetInfoWrapper")] - internal static extern uint SNISetInfo(SNIHandle pConn, QueryType QType, [In] ref uint pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNITerminate(); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIWaitForSSLHandshakeToCompleteWrapper")] - internal static extern uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint UnmanagedIsTokenRestricted([In] IntPtr token, [MarshalAs(UnmanagedType.Bool)] out bool isRestricted); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint GetSniMaxComposedSpnLength(); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, out Guid pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, [MarshalAs(UnmanagedType.Bool)] out bool pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, out ushort portNum); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - internal static extern uint SNIGetPeerAddrStrWrapper([In] SNIHandle pConn, int bufferSize, StringBuilder addrBuffer, out uint addrLen); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, out Provider provNum); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIInitialize")] - internal static extern uint SNIInitialize([In] IntPtr pmo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIOpenSyncExWrapper(ref SniClientConsumerInfo pClientConsumerInfo, out IntPtr ppConn); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIOpenWrapper( - [In] ref SniConsumerInfo pConsumerInfo, - [MarshalAs(UnmanagedType.LPWStr)] string szConnect, - [In] SNIHandle pConn, - out IntPtr ppConn, - [MarshalAs(UnmanagedType.Bool)] bool fSync, - SqlConnectionIPAddressPreference ipPreference, - [In] ref SniDnsCacheInfo pDNSCachedInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr SNIPacketAllocateWrapper([In] SafeHandle pConn, IoType IOType); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIPacketGetDataWrapper([In] IntPtr packet, [In, Out] byte[] readBuffer, uint readBufferLength, out uint dataSize); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern unsafe void SNIPacketSetData(SNIPacket pPacket, [In] byte* pbBuf, uint cbBuf); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNISecGenClientContextWrapper")] - internal static extern unsafe uint SNISecGenClientContextWrapper( - [In] SNIHandle pConn, - [In, Out] byte* pIn, - uint cbIn, - [In, Out] byte[] pOut, - [In] ref uint pcbOut, - [MarshalAsAttribute(UnmanagedType.Bool)] out bool pfDone, - byte* szServerInfo, - uint cbServerInfo, - [MarshalAsAttribute(UnmanagedType.LPWStr)] string pwszUserName, - [MarshalAsAttribute(UnmanagedType.LPWStr)] string pwszPassword); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIWriteAsyncWrapper(SNIHandle pConn, [In] SNIPacket pPacket); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIWriteSyncOverAsync(SNIHandle pConn, [In] SNIPacket pPacket); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumOpenWrapper")] - internal static extern IntPtr SNIServerEnumOpen(); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumCloseWrapper")] - internal static extern void SNIServerEnumClose([In] IntPtr packet); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumReadWrapper", CharSet = CharSet.Unicode)] - internal static extern int SNIServerEnumRead([In] IntPtr packet, - [In, Out][MarshalAs(UnmanagedType.LPArray)] char[] readBuffer, - [In] int bufferLength, - [MarshalAs(UnmanagedType.Bool)] out bool more); - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs deleted file mode 100644 index 834355b488..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeManagedWrapperX86.cs +++ /dev/null @@ -1,142 +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.Runtime.InteropServices; -using System.Text; -using Interop.Windows.Sni; -using static Microsoft.Data.SqlClient.SNINativeMethodWrapper; - -namespace Microsoft.Data.SqlClient -{ - internal static class SNINativeManagedWrapperX86 - { - private const string SNI = "Microsoft.Data.SqlClient.SNI.x86.dll"; - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIAddProviderWrapper")] - internal static extern uint SNIAddProvider(SNIHandle pConn, Provider ProvNum, [In] ref uint pInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIAddProviderWrapper")] - internal static extern uint SNIAddProviderWrapper(SNIHandle pConn, Provider ProvNum, [In] ref AuthProviderInfo pInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNICheckConnectionWrapper")] - internal static extern uint SNICheckConnection([In] SNIHandle pConn); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNICloseWrapper")] - internal static extern uint SNIClose(IntPtr pConn); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern void SNIGetLastError(out SniError pErrorStruct); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern void SNIPacketRelease(IntPtr pPacket); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIPacketResetWrapper")] - internal static extern void SNIPacketReset([In] SNIHandle pConn, IoType IOType, SNIPacket pPacket, ConsumerNumber ConsNum); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIQueryInfo(QueryType QType, ref uint pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIQueryInfo(QueryType QType, ref IntPtr pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIReadAsyncWrapper")] - internal static extern uint SNIReadAsync(SNIHandle pConn, ref IntPtr ppNewPacket); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIReadSyncOverAsync(SNIHandle pConn, ref IntPtr ppNewPacket, int timeout); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIRemoveProviderWrapper")] - internal static extern uint SNIRemoveProvider(SNIHandle pConn, Provider ProvNum); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNISecInitPackage(ref uint pcbMaxToken); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNISetInfoWrapper")] - internal static extern uint SNISetInfo(SNIHandle pConn, QueryType QType, [In] ref uint pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNITerminate(); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIWaitForSSLHandshakeToCompleteWrapper")] - internal static extern uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint UnmanagedIsTokenRestricted([In] IntPtr token, [MarshalAs(UnmanagedType.Bool)] out bool isRestricted); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint GetSniMaxComposedSpnLength(); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, out Guid pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, [MarshalAs(UnmanagedType.Bool)] out bool pbQInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, out ushort portNum); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - internal static extern uint SNIGetPeerAddrStrWrapper([In] SNIHandle pConn, int bufferSize, StringBuilder addrBuffer, out uint addrLen); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, out Provider provNum); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIInitialize")] - internal static extern uint SNIInitialize([In] IntPtr pmo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIOpenSyncExWrapper(ref SniClientConsumerInfo pClientConsumerInfo, out IntPtr ppConn); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIOpenWrapper( - [In] ref SniConsumerInfo pConsumerInfo, - [MarshalAs(UnmanagedType.LPWStr)] string szConnect, - [In] SNIHandle pConn, - out IntPtr ppConn, - [MarshalAs(UnmanagedType.Bool)] bool fSync, - SqlConnectionIPAddressPreference ipPreference, - [In] ref SniDnsCacheInfo pDNSCachedInfo); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern IntPtr SNIPacketAllocateWrapper([In] SafeHandle pConn, IoType IOType); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIPacketGetDataWrapper([In] IntPtr packet, [In, Out] byte[] readBuffer, uint readBufferLength, out uint dataSize); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern unsafe void SNIPacketSetData(SNIPacket pPacket, [In] byte* pbBuf, uint cbBuf); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNISecGenClientContextWrapper")] - internal static extern unsafe uint SNISecGenClientContextWrapper( - [In] SNIHandle pConn, - [In, Out] byte* pIn, - uint cbIn, - [In, Out] byte[] pOut, - [In] ref uint pcbOut, - [MarshalAsAttribute(UnmanagedType.Bool)] out bool pfDone, - byte* szServerInfo, - uint cbServerInfo, - [MarshalAsAttribute(UnmanagedType.LPWStr)] string pwszUserName, - [MarshalAsAttribute(UnmanagedType.LPWStr)] string pwszPassword); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIWriteAsyncWrapper(SNIHandle pConn, [In] SNIPacket pPacket); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl)] - internal static extern uint SNIWriteSyncOverAsync(SNIHandle pConn, [In] SNIPacket pPacket); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumOpenWrapper")] - internal static extern IntPtr SNIServerEnumOpen(); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumCloseWrapper")] - internal static extern void SNIServerEnumClose([In] IntPtr packet); - - [DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIServerEnumReadWrapper", CharSet = CharSet.Unicode)] - internal static extern int SNIServerEnumRead([In] IntPtr packet, - [In, Out][MarshalAs(UnmanagedType.LPArray)] char[] readBuffer, - [In] int bufferLength, - [MarshalAs(UnmanagedType.Bool)] out bool more); - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs deleted file mode 100644 index de4e344ff0..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Interop/SNINativeMethodWrapper.cs +++ /dev/null @@ -1,986 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; -using System.Security; -using System.Text; -using Interop.Windows.Sni; -using Microsoft.Data.Common; -using Microsoft.Data.SqlClient; - -namespace Microsoft.Data.SqlClient -{ - internal static class SNINativeMethodWrapper - { - private static int s_sniMaxComposedSpnLength = -1; - private static readonly System.Runtime.InteropServices.Architecture s_architecture = System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture; - - private const int SniOpenTimeOut = -1; // infinite - - internal const int SniIP6AddrStringBufferLength = 48; // from SNI layer - - internal static int SniMaxComposedSpnLength - { - get - { - if (s_sniMaxComposedSpnLength == -1) - { - s_sniMaxComposedSpnLength = checked((int)GetSniMaxComposedSpnLength()); - } - return s_sniMaxComposedSpnLength; - } - } - - static AppDomain GetDefaultAppDomainInternal() - { - return AppDomain.CurrentDomain; - } - - internal static _AppDomain GetDefaultAppDomain() - { - return GetDefaultAppDomainInternal(); - } - - [ResourceExposure(ResourceScope.Process)] // SxS: there is no way to set scope = Instance, using Process which is wider - [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] - internal unsafe static byte[] GetData() - { - int size; - IntPtr ptr = (IntPtr)(SqlDependencyProcessDispatcherStorage.NativeGetData(out size)); - byte[] result = null; - - if (ptr != IntPtr.Zero) - { - result = new byte[size]; - Marshal.Copy(ptr, result, 0, size); - } - - return result; - } - - [ResourceExposure(ResourceScope.Process)] // SxS: there is no way to set scope = Instance, using Process which is wider - [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] - internal unsafe static void SetData(Byte[] data) - { - //cli::pin_ptr pin_dispatcher = &data[0]; - fixed (byte* pin_dispatcher = &data[0]) - { - SqlDependencyProcessDispatcherStorage.NativeSetData(pin_dispatcher, data.Length); - } - } - - #region DLL Imports - internal static uint SNIAddProvider(SNIHandle pConn, Provider ProvNum, [In] ref uint pInfo) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIAddProvider(pConn, ProvNum, ref pInfo); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIAddProvider(pConn, ProvNum, ref pInfo); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIAddProvider(pConn, ProvNum, ref pInfo); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static uint SNIAddProviderWrapper(SNIHandle pConn, Provider ProvNum, [In] ref AuthProviderInfo pInfo) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIAddProviderWrapper(pConn, ProvNum, ref pInfo); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIAddProviderWrapper(pConn, ProvNum, ref pInfo); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIAddProviderWrapper(pConn, ProvNum, ref pInfo); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static uint SNICheckConnection([In] SNIHandle pConn) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNICheckConnection(pConn); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNICheckConnection(pConn); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNICheckConnection(pConn); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static uint SNIClose(IntPtr pConn) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIClose(pConn); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIClose(pConn); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIClose(pConn); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static void SNIGetLastError(out SniError pErrorStruct) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - SNINativeManagedWrapperARM64.SNIGetLastError(out pErrorStruct); - break; - case System.Runtime.InteropServices.Architecture.X64: - SNINativeManagedWrapperX64.SNIGetLastError(out pErrorStruct); - break; - case System.Runtime.InteropServices.Architecture.X86: - SNINativeManagedWrapperX86.SNIGetLastError(out pErrorStruct); - break; - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static void SNIPacketRelease(IntPtr pPacket) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - SNINativeManagedWrapperARM64.SNIPacketRelease(pPacket); - break; - case System.Runtime.InteropServices.Architecture.X64: - SNINativeManagedWrapperX64.SNIPacketRelease(pPacket); - break; - case System.Runtime.InteropServices.Architecture.X86: - SNINativeManagedWrapperX86.SNIPacketRelease(pPacket); - break; - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static void SNIPacketReset([In] SNIHandle pConn, IoType IOType, SNIPacket pPacket, ConsumerNumber ConsNum) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - SNINativeManagedWrapperARM64.SNIPacketReset(pConn, IOType, pPacket, ConsNum); - break; - case System.Runtime.InteropServices.Architecture.X64: - SNINativeManagedWrapperX64.SNIPacketReset(pConn, IOType, pPacket, ConsNum); - break; - case System.Runtime.InteropServices.Architecture.X86: - SNINativeManagedWrapperX86.SNIPacketReset(pConn, IOType, pPacket, ConsNum); - break; - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static uint SNIQueryInfo(QueryType QType, ref uint pbQInfo) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIQueryInfo(QType, ref pbQInfo); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIQueryInfo(QType, ref pbQInfo); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIQueryInfo(QType, ref pbQInfo); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static uint SNIQueryInfo(QueryType QType, ref IntPtr pbQInfo) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIQueryInfo(QType, ref pbQInfo); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIQueryInfo(QType, ref pbQInfo); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIQueryInfo(QType, ref pbQInfo); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static uint SNIReadAsync(SNIHandle pConn, ref IntPtr ppNewPacket) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIReadAsync(pConn, ref ppNewPacket); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIReadAsync(pConn, ref ppNewPacket); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIReadAsync(pConn, ref ppNewPacket); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static uint SNIReadSyncOverAsync(SNIHandle pConn, ref IntPtr ppNewPacket, int timeout) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIReadSyncOverAsync(pConn, ref ppNewPacket, timeout); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIReadSyncOverAsync(pConn, ref ppNewPacket, timeout); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIReadSyncOverAsync(pConn, ref ppNewPacket, timeout); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static uint SNIRemoveProvider(SNIHandle pConn, Provider ProvNum) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIRemoveProvider(pConn, ProvNum); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIRemoveProvider(pConn, ProvNum); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIRemoveProvider(pConn, ProvNum); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static uint SNISecInitPackage(ref uint pcbMaxToken) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNISecInitPackage(ref pcbMaxToken); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNISecInitPackage(ref pcbMaxToken); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNISecInitPackage(ref pcbMaxToken); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static uint SNISetInfo(SNIHandle pConn, QueryType QType, [In] ref uint pbQInfo) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNISetInfo(pConn, QType, ref pbQInfo); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNISetInfo(pConn, QType, ref pbQInfo); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNISetInfo(pConn, QType, ref pbQInfo); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static uint SNITerminate() - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNITerminate(); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNITerminate(); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNITerminate(); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static uint SNIWaitForSSLHandshakeToComplete([In] SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIWaitForSSLHandshakeToComplete(pConn, dwMilliseconds, out pProtocolVersion); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIWaitForSSLHandshakeToComplete(pConn, dwMilliseconds, out pProtocolVersion); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIWaitForSSLHandshakeToComplete(pConn, dwMilliseconds, out pProtocolVersion); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static uint UnmanagedIsTokenRestricted([In] IntPtr token, [MarshalAs(UnmanagedType.Bool)] out bool isRestricted) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.UnmanagedIsTokenRestricted(token, out isRestricted); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.UnmanagedIsTokenRestricted(token, out isRestricted); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.UnmanagedIsTokenRestricted(token, out isRestricted); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - private static uint GetSniMaxComposedSpnLength() - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.GetSniMaxComposedSpnLength(); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.GetSniMaxComposedSpnLength(); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.GetSniMaxComposedSpnLength(); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - private static uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, out Guid pbQInfo) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIGetInfoWrapper(pConn, QType, out pbQInfo); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIGetInfoWrapper(pConn, QType, out pbQInfo); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIGetInfoWrapper(pConn, QType, out pbQInfo); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - private static uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, [MarshalAs(UnmanagedType.Bool)] out bool pbQInfo) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIGetInfoWrapper(pConn, QType, out pbQInfo); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIGetInfoWrapper(pConn, QType, out pbQInfo); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIGetInfoWrapper(pConn, QType, out pbQInfo); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - private static uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, out ushort portNum) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIGetInfoWrapper(pConn, QType, out portNum); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIGetInfoWrapper(pConn, QType, out portNum); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIGetInfoWrapper(pConn, QType, out portNum); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - private static uint SNIGetPeerAddrStrWrapper([In] SNIHandle pConn, int bufferSize, StringBuilder addrBuffer, out uint addrLen) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIGetPeerAddrStrWrapper(pConn, bufferSize, addrBuffer, out addrLen); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIGetPeerAddrStrWrapper(pConn, bufferSize, addrBuffer, out addrLen); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIGetPeerAddrStrWrapper(pConn, bufferSize, addrBuffer, out addrLen); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - private static uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType QType, out Provider provNum) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIGetInfoWrapper(pConn, QType, out provNum); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIGetInfoWrapper(pConn, QType, out provNum); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIGetInfoWrapper(pConn, QType, out provNum); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - private static uint SNIInitialize([In] IntPtr pmo) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIInitialize(pmo); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIInitialize(pmo); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIInitialize(pmo); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - private static uint SNIOpenSyncExWrapper(ref SniClientConsumerInfo pClientConsumerInfo, out IntPtr ppConn) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIOpenSyncExWrapper(ref pClientConsumerInfo, out ppConn); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIOpenSyncExWrapper(ref pClientConsumerInfo, out ppConn); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIOpenSyncExWrapper(ref pClientConsumerInfo, out ppConn); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - private static uint SNIOpenWrapper( - [In] ref SniConsumerInfo pConsumerInfo, - [MarshalAs(UnmanagedType.LPWStr)] string szConnect, - [In] SNIHandle pConn, - out IntPtr ppConn, - [MarshalAs(UnmanagedType.Bool)] bool fSync, - SqlConnectionIPAddressPreference ipPreference, - [In] ref SniDnsCacheInfo pDNSCachedInfo) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIOpenWrapper(ref pConsumerInfo, szConnect, pConn, out ppConn, fSync, ipPreference, ref pDNSCachedInfo); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIOpenWrapper(ref pConsumerInfo, szConnect, pConn, out ppConn, fSync, ipPreference, ref pDNSCachedInfo); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIOpenWrapper(ref pConsumerInfo, szConnect, pConn, out ppConn, fSync, ipPreference, ref pDNSCachedInfo); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - private static IntPtr SNIPacketAllocateWrapper([In] SafeHandle pConn, IoType IOType) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIPacketAllocateWrapper(pConn, IOType); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIPacketAllocateWrapper(pConn, IOType); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIPacketAllocateWrapper(pConn, IOType); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - private static uint SNIPacketGetDataWrapper([In] IntPtr packet, [In, Out] byte[] readBuffer, uint readBufferLength, out uint dataSize) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIPacketGetDataWrapper(packet, readBuffer, readBufferLength, out dataSize); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIPacketGetDataWrapper(packet, readBuffer, readBufferLength, out dataSize); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIPacketGetDataWrapper(packet, readBuffer, readBufferLength, out dataSize); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - private static unsafe void SNIPacketSetData(SNIPacket pPacket, [In] byte* pbBuf, uint cbBuf) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - SNINativeManagedWrapperARM64.SNIPacketSetData(pPacket, pbBuf, cbBuf); - break; - case System.Runtime.InteropServices.Architecture.X64: - SNINativeManagedWrapperX64.SNIPacketSetData(pPacket, pbBuf, cbBuf); - break; - case System.Runtime.InteropServices.Architecture.X86: - SNINativeManagedWrapperX86.SNIPacketSetData(pPacket, pbBuf, cbBuf); - break; - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - private static unsafe uint SNISecGenClientContextWrapper( - [In] SNIHandle pConn, - [In, Out] ReadOnlySpan pIn, - [In, Out] byte[] pOut, - [In] ref uint pcbOut, - [MarshalAsAttribute(UnmanagedType.Bool)] out bool pfDone, - byte* szServerInfo, - uint cbServerInfo, - [MarshalAsAttribute(UnmanagedType.LPWStr)] string pwszUserName, - [MarshalAsAttribute(UnmanagedType.LPWStr)] string pwszPassword) - { - fixed (byte* pInPtr = pIn) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNISecGenClientContextWrapper(pConn, pInPtr, (uint)pIn.Length, pOut, ref pcbOut, out pfDone, szServerInfo, cbServerInfo, pwszUserName, pwszPassword); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNISecGenClientContextWrapper(pConn, pInPtr, (uint)pIn.Length, pOut, ref pcbOut, out pfDone, szServerInfo, cbServerInfo, pwszUserName, pwszPassword); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNISecGenClientContextWrapper(pConn, pInPtr, (uint)pIn.Length, pOut, ref pcbOut, out pfDone, szServerInfo, cbServerInfo, pwszUserName, pwszPassword); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - } - - private static uint SNIWriteAsyncWrapper(SNIHandle pConn, [In] SNIPacket pPacket) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIWriteAsyncWrapper(pConn, pPacket); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIWriteAsyncWrapper(pConn, pPacket); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIWriteAsyncWrapper(pConn, pPacket); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - private static uint SNIWriteSyncOverAsync(SNIHandle pConn, [In] SNIPacket pPacket) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIWriteSyncOverAsync(pConn, pPacket); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIWriteSyncOverAsync(pConn, pPacket); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIWriteSyncOverAsync(pConn, pPacket); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - #endregion - internal static uint SniGetConnectionId(SNIHandle pConn, ref Guid connId) - { - return SNIGetInfoWrapper(pConn, QueryType.SNI_QUERY_CONN_CONNID, out connId); - } - - internal static uint SniGetProviderNumber(SNIHandle pConn, ref Provider provNum) - { - return SNIGetInfoWrapper(pConn, QueryType.SNI_QUERY_CONN_PROVIDERNUM, out provNum); - } - - internal static uint SniGetConnectionPort(SNIHandle pConn, ref ushort portNum) - { - return SNIGetInfoWrapper(pConn, QueryType.SNI_QUERY_CONN_PEERPORT, out portNum); - } - - internal static uint SniGetConnectionIPString(SNIHandle pConn, ref string connIPStr) - { - UInt32 ret; - uint ERROR_SUCCESS = 0; - uint connIPLen = 0; - - int bufferSize = SniIP6AddrStringBufferLength; - StringBuilder addrBuffer = new StringBuilder(bufferSize); - - ret = SNIGetPeerAddrStrWrapper(pConn, bufferSize, addrBuffer, out connIPLen); - Debug.Assert(ret == ERROR_SUCCESS, "SNIGetPeerAddrStrWrapper fail"); - - connIPStr = addrBuffer.ToString(0, Convert.ToInt32(connIPLen)); - - return ret; - } - - internal static uint SNIInitialize() - { - return SNIInitialize(IntPtr.Zero); - } - - internal static IntPtr SNIServerEnumOpen() - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIServerEnumOpen(); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIServerEnumOpen(); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIServerEnumOpen(); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - internal static int SNIServerEnumRead([In] IntPtr packet, [In, Out] char[] readbuffer, int bufferLength, out bool more) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - return SNINativeManagedWrapperARM64.SNIServerEnumRead(packet, readbuffer, bufferLength, out more); - case System.Runtime.InteropServices.Architecture.X64: - return SNINativeManagedWrapperX64.SNIServerEnumRead(packet, readbuffer, bufferLength, out more); - case System.Runtime.InteropServices.Architecture.X86: - return SNINativeManagedWrapperX86.SNIServerEnumRead(packet, readbuffer, bufferLength, out more); - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static void SNIServerEnumClose([In] IntPtr packet) - { - switch (s_architecture) - { - case System.Runtime.InteropServices.Architecture.Arm64: - SNINativeManagedWrapperARM64.SNIServerEnumClose(packet); - break; - case System.Runtime.InteropServices.Architecture.X64: - SNINativeManagedWrapperX64.SNIServerEnumClose(packet); - break; - case System.Runtime.InteropServices.Architecture.X86: - SNINativeManagedWrapperX86.SNIServerEnumClose(packet); - break; - default: - throw ADP.SNIPlatformNotSupported(s_architecture.ToString()); - } - } - - internal static unsafe uint SNIOpenMarsSession(ConsumerInfo consumerInfo, SNIHandle parent, ref IntPtr pConn, bool fSync, SqlConnectionIPAddressPreference ipPreference, SQLDNSInfo cachedDNSInfo) - { - // initialize consumer info for MARS - SniConsumerInfo native_consumerInfo = new SniConsumerInfo(); - MarshalConsumerInfo(consumerInfo, ref native_consumerInfo); - - SniDnsCacheInfo native_cachedDNSInfo = new SniDnsCacheInfo(); - native_cachedDNSInfo.wszCachedFQDN = cachedDNSInfo?.FQDN; - native_cachedDNSInfo.wszCachedTcpIPv4 = cachedDNSInfo?.AddrIPv4; - native_cachedDNSInfo.wszCachedTcpIPv6 = cachedDNSInfo?.AddrIPv6; - native_cachedDNSInfo.wszCachedTcpPort = cachedDNSInfo?.Port; - - return SNIOpenWrapper(ref native_consumerInfo, "session:", parent, out pConn, fSync, ipPreference, ref native_cachedDNSInfo); - } - - internal static unsafe uint SNIOpenSyncEx( - ConsumerInfo consumerInfo, - string constring, - ref IntPtr pConn, - byte[] spnBuffer, - byte[] instanceName, - bool fOverrideCache, - bool fSync, - int timeout, - bool fParallel, - Int32 transparentNetworkResolutionStateNo, - Int32 totalTimeout, - Boolean isAzureSqlServerEndpoint, - SqlConnectionIPAddressPreference ipPreference, - SQLDNSInfo cachedDNSInfo, - string hostNameInCertificate) - { - fixed (byte* pin_instanceName = &instanceName[0]) - { - SniClientConsumerInfo clientConsumerInfo = new SniClientConsumerInfo(); - - // initialize client ConsumerInfo part first - MarshalConsumerInfo(consumerInfo, ref clientConsumerInfo.ConsumerInfo); - - clientConsumerInfo.wszConnectionString = constring; - clientConsumerInfo.HostNameInCertificate = hostNameInCertificate; - clientConsumerInfo.networkLibrary = Prefix.UNKNOWN_PREFIX; - clientConsumerInfo.szInstanceName = pin_instanceName; - clientConsumerInfo.cchInstanceName = (uint)instanceName.Length; - clientConsumerInfo.fOverrideLastConnectCache = fOverrideCache; - clientConsumerInfo.fSynchronousConnection = fSync; - clientConsumerInfo.timeout = timeout; - clientConsumerInfo.fParallel = fParallel; - - clientConsumerInfo.isAzureSqlServerEndpoint = ADP.IsAzureSqlServerEndpoint(constring); - - switch (transparentNetworkResolutionStateNo) - { - case (0): - clientConsumerInfo.transparentNetworkResolution = TransparentNetworkResolutionMode.DisabledMode; - break; - case (1): - clientConsumerInfo.transparentNetworkResolution = TransparentNetworkResolutionMode.SequentialMode; - break; - case (2): - clientConsumerInfo.transparentNetworkResolution = TransparentNetworkResolutionMode.ParallelMode; - break; - }; - clientConsumerInfo.totalTimeout = totalTimeout; - - clientConsumerInfo.ipAddressPreference = ipPreference; - clientConsumerInfo.DNSCacheInfo.wszCachedFQDN = cachedDNSInfo?.FQDN; - clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv4 = cachedDNSInfo?.AddrIPv4; - clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv6 = cachedDNSInfo?.AddrIPv6; - clientConsumerInfo.DNSCacheInfo.wszCachedTcpPort = cachedDNSInfo?.Port; - - if (spnBuffer != null) - { - fixed (byte* pin_spnBuffer = &spnBuffer[0]) - { - clientConsumerInfo.szSPN = pin_spnBuffer; - clientConsumerInfo.cchSPN = (uint)spnBuffer.Length; - return SNIOpenSyncExWrapper(ref clientConsumerInfo, out pConn); - } - } - else - { - // else leave szSPN null (SQL Auth) - return SNIOpenSyncExWrapper(ref clientConsumerInfo, out pConn); - } - } - } - - [ResourceExposure(ResourceScope.None)] - [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] - internal static uint SNIAddProvider(SNIHandle pConn, - Provider providerEnum, - AuthProviderInfo authInfo) - { - UInt32 ret; - uint ERROR_SUCCESS = 0; - - Debug.Assert(authInfo.clientCertificateCallback == null, "CTAIP support has been removed"); - - ret = SNIAddProviderWrapper(pConn, providerEnum, ref authInfo); - - if (ret == ERROR_SUCCESS) - { - // added a provider, need to requery for sync over async support - ret = SNIGetInfoWrapper(pConn, QueryType.SNI_QUERY_CONN_SUPPORTS_SYNC_OVER_ASYNC, out bool _); - Debug.Assert(ret == ERROR_SUCCESS, "SNIGetInfo cannot fail with this QType"); - } - - return ret; - } - - internal static void SNIPacketAllocate(SafeHandle pConn, IoType IOType, ref IntPtr pPacket) - { - pPacket = SNIPacketAllocateWrapper(pConn, IOType); - } - - internal static unsafe uint SNIPacketGetData(IntPtr packet, byte[] readBuffer, ref uint dataSize) - { - return SNIPacketGetDataWrapper(packet, readBuffer, (uint)readBuffer.Length, out dataSize); - } - - internal static unsafe void SNIPacketSetData(SNIPacket packet, byte[] data, int length) - { - fixed (byte* pin_data = &data[0]) - { - SNIPacketSetData(packet, pin_data, (uint)length); - } - } - - //[ResourceExposure(ResourceScope::None)] - // - // Notes on SecureString: Writing out security sensitive information to managed buffer should be avoid as these can be moved - // around by GC. There are two set of information which falls into this category: passwords and new changed password which - // are passed in as SecureString by a user. Writing out clear passwords information is delayed until this layer to ensure that - // the information is written out to buffer which is pinned in this method already. This also ensures that processing a clear password - // is done right before it is written out to SNI_Packet where gets encrypted properly. - // TdsParserStaticMethods.EncryptPassword operation is also done here to minimize the time the clear password is held in memory. Any changes - // to loose encryption algorithm is changed it should be done in both in this method as well as TdsParserStaticMethods.EncryptPassword. - // Up to current release, it is also guaranteed that both password and new change password will fit into a single login packet whose size is fixed to 4096 - // So, there is no splitting logic is needed. - internal static void SNIPacketSetData(SNIPacket packet, - Byte[] data, - Int32 length, - SecureString[] passwords, // pointer to the passwords which need to be written out to SNI Packet - Int32[] passwordOffsets // Offset into data buffer where the password to be written out to - ) - { - Debug.Assert(passwords == null || (passwordOffsets != null && passwords.Length == passwordOffsets.Length), "The number of passwords does not match the number of password offsets"); - - bool mustRelease = false; - bool mustClearBuffer = false; - IntPtr clearPassword = IntPtr.Zero; - - // provides a guaranteed finally block – without this it isn’t guaranteed – non interruptable by fatal exceptions - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - unsafe - { - - fixed (byte* pin_data = &data[0]) - { } - if (passwords != null) - { - // Process SecureString - for (int i = 0; i < passwords.Length; ++i) - { - // SecureString is used - if (passwords[i] != null) - { - // provides a guaranteed finally block – without this it isn’t guaranteed – non interruptable by fatal exceptions - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - // ========================================================================== - // Get the clear text of secure string without converting it to String type - // ========================================================================== - clearPassword = Marshal.SecureStringToCoTaskMemUnicode(passwords[i]); - - // ========================================================================================================================== - // Losely encrypt the clear text - The encryption algorithm should exactly match the TdsParserStaticMethods.EncryptPassword - // ========================================================================================================================== - - unsafe - { - - char* pwChar = (char*)clearPassword.ToPointer(); - byte* pByte = (byte*)(clearPassword.ToPointer()); - - - - - int s; - byte bLo; - byte bHi; - int passwordsLength = passwords[i].Length; - for (int j = 0; j < passwordsLength; ++j) - { - s = (int)*pwChar; - bLo = (byte)(s & 0xff); - bHi = (byte)((s >> 8) & 0xff); - *(pByte++) = (Byte)((((bLo & 0x0f) << 4) | (bLo >> 4)) ^ 0xa5); - *(pByte++) = (Byte)((((bHi & 0x0f) << 4) | (bHi >> 4)) ^ 0xa5); - ++pwChar; - } - - // =========================================================== - // Write out the losely encrypted passwords to data buffer - // =========================================================== - mustClearBuffer = true; - Marshal.Copy(clearPassword, data, passwordOffsets[i], passwordsLength * 2); - } - } - finally - { - // Make sure that we clear the security sensitive information - if (clearPassword != IntPtr.Zero) - { - Marshal.ZeroFreeCoTaskMemUnicode(clearPassword); - } - } - } - } - } - - packet.DangerousAddRef(ref mustRelease); - Debug.Assert(mustRelease, "AddRef Failed!"); - - fixed (byte* pin_data = &data[0]) - { - SNIPacketSetData(packet, pin_data, (uint)length); - } - } - } - finally - { - if (mustRelease) - { - packet.DangerousRelease(); - } - - // Make sure that we clear the security sensitive information - // data->Initialize() is not safe to call under CER - if (mustClearBuffer) - { - for (int i = 0; i < data.Length; ++i) - { - data[i] = 0; - } - } - } - } - - internal static unsafe uint SNISecGenClientContext(SNIHandle pConnectionObject, ReadOnlySpan inBuff, byte[] OutBuff, ref uint sendLength, byte[] serverUserName) - { - fixed (byte* pin_serverUserName = &serverUserName[0]) - { - return SNISecGenClientContextWrapper( - pConnectionObject, - inBuff, - OutBuff, - ref sendLength, - out bool _, - pin_serverUserName, - (uint)serverUserName.Length, - null, - null); - } - } - - internal static uint SNIWritePacket(SNIHandle pConn, SNIPacket packet, bool sync) - { - if (sync) - { - return SNIWriteSyncOverAsync(pConn, packet); - } - else - { - return SNIWriteAsyncWrapper(pConn, packet); - } - } - - private static void MarshalConsumerInfo(ConsumerInfo consumerInfo, ref SniConsumerInfo native_consumerInfo) - { - native_consumerInfo.DefaultUserDataLength = consumerInfo.defaultBufferSize; - native_consumerInfo.fnReadComp = consumerInfo.readDelegate != null - ? Marshal.GetFunctionPointerForDelegate(consumerInfo.readDelegate) - : IntPtr.Zero; - native_consumerInfo.fnWriteComp = consumerInfo.writeDelegate != null - ? Marshal.GetFunctionPointerForDelegate(consumerInfo.writeDelegate) - : IntPtr.Zero; - native_consumerInfo.ConsumerKey = consumerInfo.key; - } - } -} - -namespace Microsoft.Data -{ - internal static class Win32NativeMethods - { - internal static bool IsTokenRestrictedWrapper(IntPtr token) - { - bool isRestricted; - uint result = SNINativeMethodWrapper.UnmanagedIsTokenRestricted(token, out isRestricted); - - if (result != 0) - { - Marshal.ThrowExceptionForHR(unchecked((int)result)); - } - - return isRestricted; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/RelationshipConverter.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/RelationshipConverter.cs deleted file mode 100644 index 9718f0342c..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/RelationshipConverter.cs +++ /dev/null @@ -1,33 +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. - -namespace Microsoft.Data -{ - using System; - using System.ComponentModel; - using System.ComponentModel.Design.Serialization; - - sealed internal class RelationshipConverter : ExpandableObjectConverter - { - - // converter classes should have public ctor - public RelationshipConverter() - { - } - - /// - /// Gets a value indicating whether this converter can - /// convert an object to the given destination type using the context. - /// - public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) - { - if (destinationType == typeof(InstanceDescriptor)) - { - return true; - } - return base.CanConvertTo(context, destinationType); - } - } -} - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs deleted file mode 100644 index 43e73ab69b..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBAPI.cs +++ /dev/null @@ -1,367 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Diagnostics; -using System.Globalization; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Security; -using System.Text; -using System.Threading; -using Interop.Windows.Kernel32; -using Interop.Windows.Sni; -using Microsoft.Data.SqlClient; - -namespace Microsoft.Data -{ - internal static class LocalDBAPI - { - private const string LocalDbPrefix = @"(localdb)\"; - private const string LocalDbPrefix_NP = @"np:\\.\pipe\LOCALDB#"; - const string Const_partialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST"; - - static PermissionSet _fullTrust = null; - static bool _partialTrustFlagChecked = false; - static bool _partialTrustAllowed = false; - - - // check if name is in format (localdb)\ and return instance name if it is - internal static string GetLocalDbInstanceNameFromServerName(string serverName) - { - if (serverName is not null) - { - // it can start with spaces if specified in quotes - // Memory allocation is reduced by using ReadOnlySpan - ReadOnlySpan input = serverName.AsSpan().Trim(); - if (input.StartsWith(LocalDbPrefix.AsSpan(), StringComparison.OrdinalIgnoreCase)) - { - input = input.Slice(LocalDbPrefix.Length); - if (!input.IsEmpty) - { - return input.ToString(); - } - } - else if (input.StartsWith(LocalDbPrefix_NP.AsSpan(), StringComparison.OrdinalIgnoreCase)) - { - return input.ToString(); - } - - } - return null; - } - - internal static void ReleaseDLLHandles() - { - s_userInstanceDLLHandle = IntPtr.Zero; - s_localDBFormatMessage = null; - s_localDBCreateInstance = null; - } - - - - //This is copy of handle that SNI maintains, so we are responsible for freeing it - therefore there we are not using SafeHandle - static IntPtr s_userInstanceDLLHandle = IntPtr.Zero; - - static object s_dllLock = new object(); - - static IntPtr UserInstanceDLLHandle - { - get - { - if (s_userInstanceDLLHandle == IntPtr.Zero) - { - bool lockTaken = false; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - Monitor.Enter(s_dllLock, ref lockTaken); - if (s_userInstanceDLLHandle == IntPtr.Zero) - { - SNINativeMethodWrapper.SNIQueryInfo(QueryType.SNI_QUERY_LOCALDB_HMODULE, ref s_userInstanceDLLHandle); - if (s_userInstanceDLLHandle != IntPtr.Zero) - { - SqlClientEventSource.Log.TryTraceEvent(" LocalDB - handle obtained"); - } - else - { - SniError sniError = new SniError(); - SNINativeMethodWrapper.SNIGetLastError(out sniError); - throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_FailedGetDLLHandle"), sniError: sniError.sniError); - } - } - } - finally - { - if (lockTaken) - Monitor.Exit(s_dllLock); - } - } - return s_userInstanceDLLHandle; - } - } - - [SuppressUnmanagedCodeSecurity] - [UnmanagedFunctionPointer(CallingConvention.Cdecl)] - private delegate int LocalDBCreateInstanceDelegate([MarshalAs(UnmanagedType.LPWStr)] string version, [MarshalAs(UnmanagedType.LPWStr)] string instance, UInt32 flags); - - static LocalDBCreateInstanceDelegate s_localDBCreateInstance = null; - - static LocalDBCreateInstanceDelegate LocalDBCreateInstance - { - get - { - if (s_localDBCreateInstance == null) - { - bool lockTaken = false; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - Monitor.Enter(s_dllLock, ref lockTaken); - if (s_localDBCreateInstance == null) - { - IntPtr functionAddr = Kernel32Safe.GetProcAddress(UserInstanceDLLHandle, "LocalDBCreateInstance"); - - if (functionAddr == IntPtr.Zero) - { - int hResult = Marshal.GetLastWin32Error(); - SqlClientEventSource.Log.TryTraceEvent(" GetProcAddress for LocalDBCreateInstance error 0x{0}", hResult); - throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_MethodNotFound")); - } - s_localDBCreateInstance = (LocalDBCreateInstanceDelegate)Marshal.GetDelegateForFunctionPointer(functionAddr, typeof(LocalDBCreateInstanceDelegate)); - } - } - finally - { - if (lockTaken) - Monitor.Exit(s_dllLock); - } - } - return s_localDBCreateInstance; - } - } - - - [SuppressUnmanagedCodeSecurity] - [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] - private delegate int LocalDBFormatMessageDelegate(int hrLocalDB, UInt32 dwFlags, UInt32 dwLanguageId, StringBuilder buffer, ref UInt32 buflen); - - static LocalDBFormatMessageDelegate s_localDBFormatMessage = null; - - static LocalDBFormatMessageDelegate LocalDBFormatMessage - { - get - { - if (s_localDBFormatMessage == null) - { - bool lockTaken = false; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - Monitor.Enter(s_dllLock, ref lockTaken); - if (s_localDBFormatMessage == null) - { - IntPtr functionAddr = Kernel32Safe.GetProcAddress(UserInstanceDLLHandle, "LocalDBFormatMessage"); - - if (functionAddr == IntPtr.Zero) - { - // SNI checks for LocalDBFormatMessage during DLL loading, so it is practically impossible to get this error. - int hResult = Marshal.GetLastWin32Error(); - SqlClientEventSource.Log.TryTraceEvent(" GetProcAddress for LocalDBFormatMessage error 0x{0}", hResult); - throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_MethodNotFound")); - } - s_localDBFormatMessage = (LocalDBFormatMessageDelegate)Marshal.GetDelegateForFunctionPointer(functionAddr, typeof(LocalDBFormatMessageDelegate)); - } - } - finally - { - if (lockTaken) - Monitor.Exit(s_dllLock); - } - } - return s_localDBFormatMessage; - } - } - - const UInt32 const_LOCALDB_TRUNCATE_ERR_MESSAGE = 1;// flag for LocalDBFormatMessage that indicates that message can be truncated if it does not fit in the buffer - const int const_ErrorMessageBufferSize = 1024; // Buffer size for Local DB error message, according to Serverless team, 1K will be enough for all messages - - - internal static string GetLocalDBMessage(int hrCode) - { - Debug.Assert(hrCode < 0, "HRCode does not indicate error"); - try - { - StringBuilder buffer = new StringBuilder((int)const_ErrorMessageBufferSize); - UInt32 len = (UInt32)buffer.Capacity; - - - // First try for current culture - int hResult = LocalDBFormatMessage(hrLocalDB: hrCode, dwFlags: const_LOCALDB_TRUNCATE_ERR_MESSAGE, dwLanguageId: (UInt32)CultureInfo.CurrentCulture.LCID, - buffer: buffer, buflen: ref len); - if (hResult >= 0) - return buffer.ToString(); - else - { - // Message is not available for current culture, try default - buffer = new StringBuilder((int)const_ErrorMessageBufferSize); - len = (UInt32)buffer.Capacity; - hResult = LocalDBFormatMessage(hrLocalDB: hrCode, dwFlags: const_LOCALDB_TRUNCATE_ERR_MESSAGE, dwLanguageId: 0 /* thread locale with fallback to English */, - buffer: buffer, buflen: ref len); - if (hResult >= 0) - return buffer.ToString(); - else - return string.Format(CultureInfo.CurrentCulture, "{0} (0x{1:X}).", StringsHelper.GetString("LocalDB_UnobtainableMessage"), hResult); - } - } - catch (SqlException exc) - { - return string.Format(CultureInfo.CurrentCulture, "{0} ({1}).", StringsHelper.GetString("LocalDB_UnobtainableMessage"), exc.Message); - } - } - - - static SqlException CreateLocalDBException(string errorMessage, string instance = null, int localDbError = 0, uint sniError = 0) - { - Debug.Assert((localDbError == 0) || (sniError == 0), "LocalDB error and SNI error cannot be specified simultaneously"); - Debug.Assert(!string.IsNullOrEmpty(errorMessage), "Error message should not be null or empty"); - SqlErrorCollection collection = new SqlErrorCollection(); - - int errorCode = (localDbError == 0) ? (int)sniError : localDbError; - - if (sniError != 0) - { - string sniErrorMessage = SQL.GetSNIErrorMessage(sniError); - errorMessage = String.Format((IFormatProvider)null, "{0} (error: {1} - {2})", - errorMessage, sniError, sniErrorMessage); - } - - collection.Add(new SqlError(errorCode, 0, TdsEnums.FATAL_ERROR_CLASS, instance, errorMessage, null, 0)); - - if (localDbError != 0) - collection.Add(new SqlError(errorCode, 0, TdsEnums.FATAL_ERROR_CLASS, instance, GetLocalDBMessage(localDbError), null, 0)); - - SqlException exc = SqlException.CreateException(collection, null); - - exc._doNotReconnect = true; - - return exc; - } - - private class InstanceInfo - { - internal InstanceInfo(string version) - { - this.version = version; - this.created = false; - } - - internal readonly string version; - internal bool created; - } - - static object s_configLock = new object(); - static Dictionary s_configurableInstances = null; - - internal static void DemandLocalDBPermissions() - { - if (!_partialTrustAllowed) - { - if (!_partialTrustFlagChecked) - { - object partialTrustFlagValue = AppDomain.CurrentDomain.GetData(Const_partialTrustFlagKey); - if (partialTrustFlagValue != null && partialTrustFlagValue is bool) - { - _partialTrustAllowed = (bool)partialTrustFlagValue; - } - _partialTrustFlagChecked = true; - if (_partialTrustAllowed) - { - return; - } - } - if (_fullTrust == null) - { - _fullTrust = new NamedPermissionSet("FullTrust"); - } - _fullTrust.Demand(); - } - } - - internal static void AssertLocalDBPermissions() - { - _partialTrustAllowed = true; - } - - - internal static void CreateLocalDBInstance(string instance) - { - DemandLocalDBPermissions(); - if (s_configurableInstances == null) - { - // load list of instances from configuration, mark them as not created - bool lockTaken = false; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - Monitor.Enter(s_configLock, ref lockTaken); - if (s_configurableInstances == null) - { - Dictionary tempConfigurableInstances = new Dictionary(StringComparer.OrdinalIgnoreCase); - object section = ConfigurationManager.GetSection("system.data.localdb"); - if (section != null) // if no section just skip creation - { - // validate section type - LocalDBConfigurationSection configSection = section as LocalDBConfigurationSection; - if (configSection == null) - throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_BadConfigSectionType")); - foreach (LocalDBInstanceElement confElement in configSection.LocalDbInstances) - { - Debug.Assert(confElement.Name != null && confElement.Version != null, "Both name and version should not be null"); - tempConfigurableInstances.Add(confElement.Name.Trim(), new InstanceInfo(confElement.Version.Trim())); - } - } - else - { - SqlClientEventSource.Log.TryTraceEvent(" No system.data.localdb section found in configuration"); - } - s_configurableInstances = tempConfigurableInstances; - } - } - finally - { - if (lockTaken) - Monitor.Exit(s_configLock); - } - } - - InstanceInfo instanceInfo = null; - - if (!s_configurableInstances.TryGetValue(instance, out instanceInfo)) - return; // instance name was not in the config - - if (instanceInfo.created) - return; // instance has already been created - - Debug.Assert(!instance.Contains("\0"), "Instance name should contain embedded nulls"); - - if (instanceInfo.version.Contains("\0")) - throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_InvalidVersion"), instance: instance); - - // LocalDBCreateInstance is thread- and cross-process safe method, it is OK to call from two threads simultaneously - int hr = LocalDBCreateInstance(instanceInfo.version, instance, flags: 0); - SqlClientEventSource.Log.TryTraceEvent(" Starting creation of instance {0} version {1}", instance, instanceInfo.version); - - if (hr < 0) - { - throw CreateLocalDBException(errorMessage: StringsHelper.GetString("LocalDB_CreateFailed"), instance: instance, localDbError: hr); - } - - SqlClientEventSource.Log.TryTraceEvent(" Finished creation of instance {0}", instance); - instanceInfo.created = true; // mark instance as created - } // CreateLocalDbInstance - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBConfig.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBConfig.cs deleted file mode 100644 index 4c830520d9..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/LocalDBConfig.cs +++ /dev/null @@ -1,81 +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. - -namespace Microsoft.Data -{ - using System; - using System.Collections; - using System.Configuration; - - internal sealed class LocalDBInstanceElement : ConfigurationElement - { - [ConfigurationProperty("name", IsRequired = true)] - public string Name - { - get - { - return this["name"] as string; - } - } - - [ConfigurationProperty("version", IsRequired = true)] - public string Version - { - get - { - return this["version"] as string; - } - } - } - - internal sealed class LocalDBInstancesCollection : ConfigurationElementCollection - { - - private class TrimOrdinalIgnoreCaseStringComparer : IComparer - { - public int Compare(object x, object y) - { - string xStr = x as string; - if (xStr != null) - x = xStr.Trim(); - - string yStr = y as string; - if (yStr != null) - y = yStr.Trim(); - - return StringComparer.OrdinalIgnoreCase.Compare(x, y); - } - } - - static readonly TrimOrdinalIgnoreCaseStringComparer s_comparer = new TrimOrdinalIgnoreCaseStringComparer(); - - internal LocalDBInstancesCollection() - : base(s_comparer) - { - } - - protected override ConfigurationElement CreateNewElement() - { - return new LocalDBInstanceElement(); - } - - protected override object GetElementKey(ConfigurationElement element) - { - return ((LocalDBInstanceElement)element).Name; - } - - } - - internal sealed class LocalDBConfigurationSection : ConfigurationSection - { - [ConfigurationProperty("localdbinstances", IsRequired = true)] - public LocalDBInstancesCollection LocalDbInstances - { - get - { - return (LocalDBInstancesCollection)this["localdbinstances"] ?? new LocalDBInstancesCollection(); - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryLogicManager.LoadType.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryLogicManager.LoadType.cs deleted file mode 100644 index 922006b47f..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryLogicManager.LoadType.cs +++ /dev/null @@ -1,30 +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.Reflection; - -namespace Microsoft.Data.SqlClient -{ - /// - /// Configurable retry logic loader - /// - internal sealed partial class SqlConfigurableRetryLogicLoader - { - /// - /// Performs a case-sensitive search to resolve the specified type name. - /// - /// - /// Resolved type if it could resolve the type; otherwise, the `SqlConfigurableRetryFactory` type. - private static Type LoadType(string fullyQualifiedName) - { - string methodName = nameof(LoadType); - - var result = Type.GetType(fullyQualifiedName); - SqlClientEventSource.Log.TryTraceEvent(" The '{2}' type is resolved." - , TypeName, methodName, result?.FullName); - return result != null ? result : typeof(SqlConfigurableRetryFactory); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiConnection.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiConnection.cs deleted file mode 100644 index bf04a3a23f..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiConnection.cs +++ /dev/null @@ -1,99 +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.Data; - -namespace Microsoft.Data.SqlClient.Server -{ - - internal abstract class SmiConnection : IDisposable - { - - // - // Miscellaneous directives / accessors - // - - internal abstract string GetCurrentDatabase( - SmiEventSink eventSink - ); - - internal abstract void SetCurrentDatabase( - string databaseName, - SmiEventSink eventSink - ); - - // - // IDisposable - // - public virtual void Dispose() - { - // Obsoleting from SMI -- use Close( SmiEventSink ) instead. - // Intended to be removed (along with inheriting IDisposable) prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void Close( - SmiEventSink eventSink - ) - { - // Adding as of V3 - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V2- and hasn't implemented V3 yet. - // 2) Server didn't implement V3 on some interface, but negotiated V3+. - Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - - // - // Transaction API (should we encapsulate in it's own class or interface?) - // - internal abstract void BeginTransaction( - string name, - IsolationLevel level, - SmiEventSink eventSink - ); - - internal abstract void CommitTransaction( - long transactionId, - SmiEventSink eventSink - ); - - internal abstract void CreateTransactionSavePoint( - long transactionId, - string name, - SmiEventSink eventSink - ); - - internal abstract byte[] GetDTCAddress( // better buffer management needed? I.e. non-allocating call needed/possible? - SmiEventSink eventSink - ); - - internal abstract void EnlistTransaction( - byte[] token, // better buffer management needed? I.e. non-allocating call needed/possible? - SmiEventSink eventSink - ); - - internal abstract byte[] PromoteTransaction( // better buffer management needed? I.e. non-allocating call needed/possible? - long transactionId, - SmiEventSink eventSink - ); - - internal abstract void RollbackTransaction( - long transactionId, - string savePointName, // only roll back to save point if name non-null - SmiEventSink eventSink - ); - - } -} - - - - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiContext.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiContext.cs deleted file mode 100644 index efdb16e287..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiContext.cs +++ /dev/null @@ -1,77 +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.Data; -using System.Data.SqlTypes; -using System.Security.Principal; - -namespace Microsoft.Data.SqlClient.Server -{ - - // NOTE: connection, transaction and context pipe operations could be - // encapsulated in their own classes, and should if they get complex - // (transaction is borderline at this point). - internal abstract class SmiContext - { - - internal abstract event EventHandler OutOfScope; - - internal abstract SmiConnection ContextConnection { get; } - - internal abstract long ContextTransactionId { get; } - - internal abstract System.Transactions.Transaction ContextTransaction { get; } - - internal abstract bool HasContextPipe { get; } - - internal abstract WindowsIdentity WindowsIdentity { get; } - - internal abstract SmiRecordBuffer CreateRecordBuffer( - SmiExtendedMetaData[] columnMetaData, // Extended metadata because it requires names, udttypename and xmlschemaname ignored - SmiEventSink eventSink - ); - - internal abstract SmiRequestExecutor CreateRequestExecutor( - string commandText, - CommandType commandType, - SmiParameterMetaData[] parameterMetaData, - SmiEventSink eventSink - ); - - // TODO: Consider making ContextConnection, TransactionId and TriggerInfo simply objects you get from the context. - - internal abstract object GetContextValue(int key); - - internal abstract void GetTriggerInfo( - SmiEventSink eventSink, - out bool[] columnsUpdated, - out TriggerAction action, - out SqlXml eventInstanceData - ); - - internal abstract void SendMessageToPipe(string message, SmiEventSink eventSink); - - internal abstract void SendResultsStartToPipe(SmiRecordBuffer recordBuffer, SmiEventSink eventSink); - - internal abstract void SendResultsRowToPipe(SmiRecordBuffer recordBuffer, SmiEventSink eventSink); - - internal abstract void SendResultsEndToPipe(SmiRecordBuffer recordBuffer, SmiEventSink eventSink); - - internal abstract void SetContextValue(int key, object value); - - // Scratch LOB storage region - internal virtual SmiStream GetScratchStream(SmiEventSink sink) - { - // Adding as of V3 - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V2- and hasn't implemented V3 yet. - // 2) Server didn't implement V3, but negotiated V3+. - Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - return null; - } - } -} - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiContextFactory.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiContextFactory.cs deleted file mode 100644 index 820c9c1bd2..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiContextFactory.cs +++ /dev/null @@ -1,155 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Diagnostics; - -namespace Microsoft.Data.SqlClient.Server -{ - - sealed internal class SmiContextFactory - { - public static readonly SmiContextFactory Instance = new SmiContextFactory(); - - private readonly SmiLink _smiLink; - private readonly ulong _negotiatedSmiVersion; - private readonly byte _majorVersion; - private readonly byte _minorVersion; - private readonly short _buildNum; - private readonly string _serverVersion; - private readonly SmiEventSink_Default _eventSinkForGetCurrentContext; - - internal const ulong Sql2005Version = 100; - internal const ulong Sql2008Version = 210; - internal const ulong LatestVersion = Sql2008Version; - - private readonly ulong[] __supportedSmiVersions = new ulong[] { Sql2005Version, Sql2008Version }; - - // Used as the key for SmiContext.GetContextValue() - internal enum ContextKey - { - Connection = 0, - SqlContext = 1 - } - - - private SmiContextFactory() - { - if (InOutOfProcHelper.InProc) - { - Type smiLinkType = Type.GetType("Microsoft.SqlServer.Server.InProcLink, SqlAccess, PublicKeyToken=89845dcd8080cc91"); - - if (smiLinkType == null) - { - Debug.Assert(false, "could not get InProcLink type"); - throw SQL.ContextUnavailableOutOfProc(); // Must not be a valid version of Sql Server. - } - - System.Reflection.FieldInfo instanceField = GetStaticField(smiLinkType, "Instance"); - if (instanceField != null) - { - _smiLink = (SmiLink)GetValue(instanceField); - } - else - { - Debug.Assert(false, "could not get InProcLink.Instance"); - throw SQL.ContextUnavailableOutOfProc(); // Must not be a valid version of Sql Server. - } - - System.Reflection.FieldInfo buildVersionField = GetStaticField(smiLinkType, "BuildVersion"); - if (buildVersionField != null) - { - UInt32 buildVersion = (UInt32)GetValue(buildVersionField); - - _majorVersion = (byte)(buildVersion >> 24); - _minorVersion = (byte)((buildVersion >> 16) & 0xff); - _buildNum = (short)(buildVersion & 0xffff); - _serverVersion = (String.Format((IFormatProvider)null, "{0:00}.{1:00}.{2:0000}", _majorVersion, (short)_minorVersion, _buildNum)); - } - else - { - _serverVersion = String.Empty; // default value if nothing exists. - } - _negotiatedSmiVersion = _smiLink.NegotiateVersion(SmiLink.InterfaceVersion); - bool isSupportedVersion = false; - for (int i = 0; !isSupportedVersion && i < __supportedSmiVersions.Length; i++) - { - if (__supportedSmiVersions[i] == _negotiatedSmiVersion) - { - isSupportedVersion = true; - } - } - - // Disconnect if we didn't get a supported version!! - if (!isSupportedVersion) - { - _smiLink = null; - } - - _eventSinkForGetCurrentContext = new SmiEventSink_Default(); - } - } - - internal ulong NegotiatedSmiVersion - { - get - { - if (_smiLink == null) - { - throw SQL.ContextUnavailableOutOfProc(); // Must not be a valid version of Sql Server, or not be SqlCLR - } - - return _negotiatedSmiVersion; - } - } - - internal string ServerVersion - { - get - { - if (_smiLink == null) - { - throw SQL.ContextUnavailableOutOfProc(); // Must not be a valid version of Sql Server, or not be SqlCLR - } - - return _serverVersion; - } - } - - internal SmiContext GetCurrentContext() - { - if (_smiLink == null) - { - throw SQL.ContextUnavailableOutOfProc(); // Must not be a valid version of Sql Server, or not be SqlCLR - } - - object result = _smiLink.GetCurrentContext(_eventSinkForGetCurrentContext); - _eventSinkForGetCurrentContext.ProcessMessagesAndThrow(); - - if (result == null) - { - throw SQL.ContextUnavailableWhileInProc(); - } - - Debug.Assert(typeof(SmiContext).IsInstanceOfType(result), "didn't get SmiContext from GetCurrentContext?"); - return (SmiContext)result; - } - - [System.Security.Permissions.ReflectionPermission(System.Security.Permissions.SecurityAction.Assert, MemberAccess = true)] - private object GetValue(System.Reflection.FieldInfo fieldInfo) - { - object result = fieldInfo.GetValue(null); - return result; - } - - [System.Security.Permissions.ReflectionPermission(System.Security.Permissions.SecurityAction.Assert, MemberAccess = true)] - private System.Reflection.FieldInfo GetStaticField(Type aType, string fieldName) - { - System.Reflection.FieldInfo result = aType.GetField(fieldName, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.GetField); - return result; - } - - } -} - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiEventSink_DeferedProcessing.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiEventSink_DeferedProcessing.cs deleted file mode 100644 index c777fd470e..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiEventSink_DeferedProcessing.cs +++ /dev/null @@ -1,39 +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. - -namespace Microsoft.Data.SqlClient.Server -{ - - // This class exists purely to defer processing of messages until a later time. - // It is designed to allow calling common code that interacts with the SMI layers - // without throwing or otherwise processing messages in the sink until later on. - // - // Main example: - // SqlCommand.ExecuteNonQuerySmi calls EventStream.ProcessEvent with it's command event sink (CES) - // ProcessEvent calls OnParametersAvailable on the CES - // OnParametersAvailable sets up a deferedprocessing event sink (DPES) with the CES as its parent - // OnParametersAvailable calls ValueUtils to extract param values passing the DPES - // ValueUtils calls Smi passing DPES - // Smi may call MessagePosted, which will send a message up the sink parent chain and save it. - // ValueUtils calls ProcessMessagesAndThrow on DPES, which skips handling - // ... return up the stack ... - // SqlCommand.ExecuteNonQuerySmi calls CES.ProcessMessagesAndThrow, which handles the messages - // sent from the Smi value extraction code. - // - // IMPORTANT: Code that uses the DeferedProccess event sink is responsible for ensuring that - // these messages ARE processed at some point. - internal class SmiEventSink_DeferedProcessing : SmiEventSink_Default - { - internal SmiEventSink_DeferedProcessing(SmiEventSink parent) : base(parent) - { - } - - protected override void DispatchMessages(bool ignoreNonFatalMessages) - { - // Skip processing messages. Since messages are sent to parent and calling code will call - // ProcessMessages against parent, messages ARE NOT LOST! - } - } -} - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiEventStream.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiEventStream.cs deleted file mode 100644 index 1aab8871f2..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiEventStream.cs +++ /dev/null @@ -1,29 +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; - -namespace Microsoft.Data.SqlClient.Server -{ - internal abstract class SmiEventStream : IDisposable - { - - internal abstract bool HasEvents { get; } - - internal abstract void Close(SmiEventSink sink); - - public virtual void Dispose() - { - // Obsoleting from SMI -- use Close instead. - // Intended to be removed (along with inheriting IDisposable) prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - internal abstract void ProcessEvent(SmiEventSink sink); - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiLink.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiLink.cs deleted file mode 100644 index cda6937ec0..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiLink.cs +++ /dev/null @@ -1,29 +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. - -[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("SqlAccess, PublicKey=0024000004800000940000000602000000240000525341310004000001000100272736ad6e5f9586bac2d531eabc3acc666c2f8ec879fa94f8f7b0327d2ff2ed523448f83c3d5c5dd2dfc7bc99c5286b2c125117bf5cbe242b9d41750732b2bdffe649c6efb8e5526d526fdd130095ecdb7bf210809c6cdad8824faa9ac0310ac3cba2aa0523567b2dfa7fe250b30facbd62d4ec99b94ac47c7d3b28f1f6e4c8")] // SQLBU 437687 - -namespace Microsoft.Data.SqlClient.Server -{ - internal abstract class SmiLink - { - - // NOTE: Except for changing the constant value below, adding new members - // to this class may create versioning issues. We would prefer not - // to add additional items to this if possible. - - internal const ulong InterfaceVersion = 210; - - // Version negotiation (appdomain-wide negotiation) - // This needs to be the first method called when negotiating with a - // driver back end. Once called, the rest of the back end interface - // needs to conform to the returned version number's interface. - internal abstract ulong NegotiateVersion(ulong requestedVersion); - - // Get utility class valid in current thread execution environment - // This returns an object, to allow us to version the context without - // having to version this class. (eg. SmiContext1 vs SmiContext2) - internal abstract object GetCurrentContext(SmiEventSink eventSink); - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiRequestExecutor.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiRequestExecutor.cs deleted file mode 100644 index ed9754447d..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiRequestExecutor.cs +++ /dev/null @@ -1,940 +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.Data; -using System.Data.SqlTypes; -using System.Transactions; - -namespace Microsoft.Data.SqlClient.Server -{ - - internal abstract class SmiRequestExecutor : SmiTypedGetterSetter, ITypedSettersV3, ITypedSetters, ITypedGetters, IDisposable - { - - #region SMI active methods as of V210 - - #region Overall control methods - public virtual void Close( - SmiEventSink eventSink - ) - { - // Adding as of V3 - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V2- and hasn't implemented V3 yet. - // 2) Server didn't implement V3 on some interface, but negotiated V3+. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - internal virtual SmiEventStream Execute( - SmiConnection connection, // Assigned connection - long transactionId, // Assigned transaction - Transaction associatedTransaction, // SysTx transaction associated with request, if any. - CommandBehavior behavior, // CommandBehavior, - SmiExecuteType executeType // Type of execute called (NonQuery/Pipe/Reader/Row, etc) - ) - { - // Adding as of V210 - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V200- and hasn't implemented V210 yet. - // 2) Server didn't implement V210 on some interface, but negotiated V210+. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - #endregion - - #region Supported access method types (Get] vs. Set) - - // RequestExecutor only supports setting parameter values, not getting - internal override bool CanGet - { - get - { - return false; - } - } - - internal override bool CanSet - { - get - { - return true; - } - } - - #endregion - - // SmiRequestExecutor and it's subclasses should NOT override Getters from SmiTypedGetterSetter - // Calls against those methods on a Request Executor are not allowed. - - #region Value setters - - // Set DEFAULT bit for parameter - internal abstract void SetDefault(int ordinal); - - // SmiRequestExecutor subclasses must implement all Setters from SmiTypedGetterSetter - // SmiRequestExecutor itself does not need to implement these, since it inherits the default implementation from - // SmiTypedGetterSetter - - #endregion - #endregion - - #region Obsolete as of V210 - internal virtual SmiEventStream Execute( - SmiConnection connection, // Assigned connection - long transactionId, // Assigned transaction - CommandBehavior behavior, // CommandBehavior, - SmiExecuteType executeType // Type of execute called (NonQuery/Pipe/Reader/Row, etc) - ) - { - // Obsoleting as of V210 - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V210+ (and doesn't implement it). - // 2) Server doesn't implement this method, but negotiated V200-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - #endregion - - #region OBSOLETE STUFF that never shipped without obsolete attribute - - // - // IDisposable - // - public virtual void Dispose() - { - // ******** OBSOLETING from SMI -- use close instead. - // Intended to be removed (along with removing inheriting IDisposable) prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - // Check to see if parameter's DEFAULT bit is set - internal virtual bool IsSetAsDefault(int ordinal) - { - // ******** OBSOLETING from SMI -- Not needed. - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - // Get the count of parameters - public virtual int Count - { - get - { - // ******** OBSOLETING from SMI -- front end needs to keep track of input param metadata itself. Outparam metadata comes with ParametersAvailable event. - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - } - - // Get the meta data associated with the parameter. - public virtual SmiParameterMetaData GetMetaData(int ordinal) - { - // ******** OBSOLETING from SMI -- front end needs to keep track of input param metadata itself. Outparam metadata comes with ParametersAvailable event. - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - // - // ITypedGetters methods (for output parameters) (OBSOLETE) - // - public virtual bool IsDBNull(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlDbType GetVariantType(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual Boolean GetBoolean(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual Byte GetByte(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual long GetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual Char GetChar(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual long GetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual Int16 GetInt16(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual Int32 GetInt32(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual Int64 GetInt64(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual Single GetFloat(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual Double GetDouble(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual String GetString(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual Decimal GetDecimal(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual DateTime GetDateTime(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual Guid GetGuid(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlBoolean GetSqlBoolean(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlByte GetSqlByte(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlInt16 GetSqlInt16(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlInt32 GetSqlInt32(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlInt64 GetSqlInt64(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlSingle GetSqlSingle(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlDouble GetSqlDouble(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlMoney GetSqlMoney(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlDateTime GetSqlDateTime(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlDecimal GetSqlDecimal(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlString GetSqlString(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlBinary GetSqlBinary(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlGuid GetSqlGuid(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlChars GetSqlChars(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlBytes GetSqlBytes(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlXml GetSqlXml(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlXml GetSqlXmlRef(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlBytes GetSqlBytesRef(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlChars GetSqlCharsRef(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedGettersV3 in ParametersAvailable event instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - // - // ITypedSetters methods - // - public virtual void SetDBNull(int ordinal) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetBoolean(int ordinal, Boolean value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetByte(int ordinal, Byte value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetChar(int ordinal, char value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetInt16(int ordinal, Int16 value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetInt32(int ordinal, Int32 value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetInt64(int ordinal, Int64 value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetFloat(int ordinal, Single value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetDouble(int ordinal, Double value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetString(int ordinal, string value) - { - // Implemented as virtual method to allow transport to remove it's implementation - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V2 and dropped support for V1. - // 2) Server didn't implement V1 on some interface and negotiated V1. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetString(int ordinal, string value, int offset) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetDecimal(int ordinal, Decimal value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetDateTime(int ordinal, DateTime value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetGuid(int ordinal, Guid value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlBoolean(int ordinal, SqlBoolean value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlByte(int ordinal, SqlByte value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlInt16(int ordinal, SqlInt16 value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlInt32(int ordinal, SqlInt32 value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlInt64(int ordinal, SqlInt64 value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlSingle(int ordinal, SqlSingle value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlDouble(int ordinal, SqlDouble value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlMoney(int ordinal, SqlMoney value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlDateTime(int ordinal, SqlDateTime value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlDecimal(int ordinal, SqlDecimal value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlString(int ordinal, SqlString value) - { - // Implemented as empty virtual method to allow transport to remove it's implementation - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V2 and dropped support for V1. - // 2) Server didn't implement V1 on some interface and negotiated V1. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlString(int ordinal, SqlString value, int offset) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlBinary(int ordinal, SqlBinary value) - { - // Implemented as empty virtual method to allow transport to remove it's implementation - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V2 and dropped support for V1. - // 2) Server didn't implement V1 on some interface and negotiated V1. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlBinary(int ordinal, SqlBinary value, int offset) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlGuid(int ordinal, SqlGuid value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlChars(int ordinal, SqlChars value) - { - // Implemented as empty virtual method to allow transport to remove it's implementation - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V2 and dropped support for V1. - // 2) Server didn't implement V1 on some interface and negotiated V1. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlChars(int ordinal, SqlChars value, int offset) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlBytes(int ordinal, SqlBytes value) - { - // Implemented as empty virtual method to allow transport to remove it's implementation - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V2 and dropped support for V1. - // 2) Server didn't implement V1 on some interface and negotiated V1. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlBytes(int ordinal, SqlBytes value, int offset) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlXml(int ordinal, SqlXml value) - { - // ******** OBSOLETING from SMI -- use related ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - #endregion - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiStream.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiStream.cs deleted file mode 100644 index deed3b8f14..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiStream.cs +++ /dev/null @@ -1,47 +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. - -// Stream-like object that uses SmiEventSink for server-side errors. - -using System.IO; - -namespace Microsoft.Data.SqlClient.Server -{ - internal abstract class SmiStream - { - public abstract bool CanRead - { - get; - } - - // If CanSeek is false, Position, Seek, Length, and SetLength should throw. - public abstract bool CanSeek - { - get; - } - - public abstract bool CanWrite - { - get; - } - - public abstract long GetLength(SmiEventSink sink); - - public abstract long GetPosition(SmiEventSink sink); - public abstract void SetPosition(SmiEventSink sink, long position); - - public abstract void Flush(SmiEventSink sink); - - public abstract long Seek(SmiEventSink sink, long offset, SeekOrigin origin); - - public abstract void SetLength(SmiEventSink sink, long value); - - public abstract int Read(SmiEventSink sink, byte[] buffer, int offset, int count); - public abstract void Write(SmiEventSink sink, byte[] buffer, int offset, int count); - - public abstract int Read(SmiEventSink sink, char[] buffer, int offset, int count); - public abstract void Write(SmiEventSink sink, char[] buffer, int offset, int count); - } - -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiXetterAccessMap.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiXetterAccessMap.cs deleted file mode 100644 index a5b9f2004d..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiXetterAccessMap.cs +++ /dev/null @@ -1,63 +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.Diagnostics; - -namespace Microsoft.Data.SqlClient.Server -{ - - // Formal encoding of SMI's metadata-to-ITypedSetter/-from-ITypedGetter validity rules - internal partial class SmiXetterAccessMap - { - - private static bool[,] __isGetterAccessValid = { - // Getters as columns (abbreviated from XetterTypeCode names) - // SqlDbTypes as rows - // bool, byte, bytes, chars, strng, int16, int32, int64, singl, doubl, sqldec, date, guid, varmd, Xetr, time, dtost -/*BigInt*/ { _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*Binary*/ { _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*Bit*/ { X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*Char*/ { _ , _ , X , X , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*DTime*/ { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , }, -/*Decimal*/ { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , }, -/*Float*/ { _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , }, -/*Image*/ { _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*Int*/ { _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*Money*/ { _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*NChar*/ { _ , _ , X , X , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*NText*/ { _ , _ , X , X , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*NVarChar*/{ _ , _ , X , X , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*Real*/ { _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*UniqueId*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , }, -/*SmDTime*/ { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , }, -/*SmInt*/ { _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*SmMoney*/ { _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*Text*/ { _ , _ , X , X , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*Tstamp*/ { _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*TinyInt*/ { _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*VarBin*/ { _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*VarChar*/ { _ , _ , X , X , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*Variant*/ { X , X , X , X , X , X , X , X , X , X , X , X , X , X , _ , X , X , }, -/* 24 */ { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*Xml*/ { _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/* 26 */ { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/* 27 */ { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/* 28 */ { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*Udt*/ { _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, -/*Struct*/ { _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , }, -/*Date*/ { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , }, -/*Time*/ { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , }, -/*DTime2*/ { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , }, -/*DTOffset*/{ _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , }, - }; - - internal static bool IsGetterAccessValid(SmiMetaData metaData, SmiXetterTypeCode xetterType) - { - // Make sure no-one adds a new xetter type without updating this file! - Debug.Assert(SmiXetterTypeCode.XetBoolean <= xetterType && SmiXetterTypeCode.XetDateTimeOffset >= xetterType); - - return __isGetterAccessValid[(int)metaData.SqlDbType, (int)xetterType]; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/TriggerAction.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/TriggerAction.cs deleted file mode 100644 index 1f1b011ee1..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/TriggerAction.cs +++ /dev/null @@ -1,266 +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. - -namespace Microsoft.Data.SqlClient.Server -{ - internal enum EMDEventType - { - x_eet_Invalid = 0, // Not actually a valid type: not persisted. - x_eet_Insert = 1, // Insert (eg. on table/view) - x_eet_Update = 2, // Update (eg. on table/view) - x_eet_Delete = 3, // Delete (eg. on table/view) - x_eet_Create_Table = 21, - x_eet_Alter_Table = 22, - x_eet_Drop_Table = 23, - x_eet_Create_Index = 24, - x_eet_Alter_Index = 25, - x_eet_Drop_Index = 26, - x_eet_Create_Stats = 27, - x_eet_Update_Stats = 28, - x_eet_Drop_Stats = 29, - x_eet_Create_Secexpr = 31, - x_eet_Drop_Secexpr = 33, - x_eet_Create_Synonym = 34, - x_eet_Drop_Synonym = 36, - x_eet_Create_View = 41, - x_eet_Alter_View = 42, - x_eet_Drop_View = 43, - x_eet_Create_Procedure = 51, - x_eet_Alter_Procedure = 52, - x_eet_Drop_Procedure = 53, - x_eet_Create_Function = 61, - x_eet_Alter_Function = 62, - x_eet_Drop_Function = 63, - x_eet_Create_Trigger = 71, // On database/server/table - x_eet_Alter_Trigger = 72, - x_eet_Drop_Trigger = 73, - x_eet_Create_Event_Notification = 74, - x_eet_Drop_Event_Notification = 76, - x_eet_Create_Type = 91, - // x_eet_Alter_Type = 92, - x_eet_Drop_Type = 93, - x_eet_Create_Assembly = 101, - x_eet_Alter_Assembly = 102, - x_eet_Drop_Assembly = 103, - x_eet_Create_User = 131, - x_eet_Alter_User = 132, - x_eet_Drop_User = 133, - x_eet_Create_Role = 134, - x_eet_Alter_Role = 135, - x_eet_Drop_Role = 136, - x_eet_Create_AppRole = 137, - x_eet_Alter_AppRole = 138, - x_eet_Drop_AppRole = 139, - x_eet_Create_Schema = 141, - x_eet_Alter_Schema = 142, - x_eet_Drop_Schema = 143, - x_eet_Create_Login = 144, - x_eet_Alter_Login = 145, - x_eet_Drop_Login = 146, - x_eet_Create_MsgType = 151, - x_eet_Alter_MsgType = 152, - x_eet_Drop_MsgType = 153, - x_eet_Create_Contract = 154, - x_eet_Alter_Contract = 155, - x_eet_Drop_Contract = 156, - x_eet_Create_Queue = 157, - x_eet_Alter_Queue = 158, - x_eet_Drop_Queue = 159, - x_eet_Create_Service = 161, - x_eet_Alter_Service = 162, - x_eet_Drop_Service = 163, - x_eet_Create_Route = 164, - x_eet_Alter_Route = 165, - x_eet_Drop_Route = 166, - x_eet_Grant_Statement = 167, - x_eet_Deny_Statement = 168, - x_eet_Revoke_Statement = 169, - x_eet_Grant_Object = 170, - x_eet_Deny_Object = 171, - x_eet_Revoke_Object = 172, - x_eet_Activation = 173, - x_eet_Create_Binding = 174, - x_eet_Alter_Binding = 175, - x_eet_Drop_Binding = 176, - x_eet_Create_XmlSchema = 177, - x_eet_Alter_XmlSchema = 178, - x_eet_Drop_XmlSchema = 179, - x_eet_Create_HttpEndpoint = 181, - x_eet_Alter_HttpEndpoint = 182, - x_eet_Drop_HttpEndpoint = 183, - x_eet_Create_Partition_Function = 191, - x_eet_Alter_Partition_Function = 192, - x_eet_Drop_Partition_Function = 193, - x_eet_Create_Partition_Scheme = 194, - x_eet_Alter_Partition_Scheme = 195, - x_eet_Drop_Partition_Scheme = 196, - - x_eet_Create_Database = 201, - x_eet_Alter_Database = 202, - x_eet_Drop_Database = 203, - - // 1000 - 1999 is reserved for SQLTrace. - x_eet_Trace_Start = 1000, - x_eet_Trace_End = 1999, - // WHEN ADDING, PLEASE CHECK WITH FILE-OWNER FOR WHICH NUMBERS TO USE. THANKS! - }; - - /// - public enum TriggerAction - { - /// - Invalid = EMDEventType.x_eet_Invalid, - /// - Insert = EMDEventType.x_eet_Insert, - /// - Update = EMDEventType.x_eet_Update, - /// - Delete = EMDEventType.x_eet_Delete, - /// - CreateTable = EMDEventType.x_eet_Create_Table, - /// - AlterTable = EMDEventType.x_eet_Alter_Table, - /// - DropTable = EMDEventType.x_eet_Drop_Table, - /// - CreateIndex = EMDEventType.x_eet_Create_Index, - /// - AlterIndex = EMDEventType.x_eet_Alter_Index, - /// - DropIndex = EMDEventType.x_eet_Drop_Index, - /// - CreateSynonym = EMDEventType.x_eet_Create_Synonym, - /// - DropSynonym = EMDEventType.x_eet_Drop_Synonym, - /// - CreateSecurityExpression = EMDEventType.x_eet_Create_Secexpr, - /// - DropSecurityExpression = EMDEventType.x_eet_Drop_Secexpr, - /// - CreateView = EMDEventType.x_eet_Create_View, - /// - AlterView = EMDEventType.x_eet_Alter_View, - /// - DropView = EMDEventType.x_eet_Drop_View, - /// - CreateProcedure = EMDEventType.x_eet_Create_Procedure, - /// - AlterProcedure = EMDEventType.x_eet_Alter_Procedure, - /// - DropProcedure = EMDEventType.x_eet_Drop_Procedure, - /// - CreateFunction = EMDEventType.x_eet_Create_Function, - /// - AlterFunction = EMDEventType.x_eet_Alter_Function, - /// - DropFunction = EMDEventType.x_eet_Drop_Function, - /// - CreateTrigger = EMDEventType.x_eet_Create_Trigger, - /// - AlterTrigger = EMDEventType.x_eet_Alter_Trigger, - /// - DropTrigger = EMDEventType.x_eet_Drop_Trigger, - /// - CreateEventNotification = EMDEventType.x_eet_Create_Event_Notification, - /// - DropEventNotification = EMDEventType.x_eet_Drop_Event_Notification, - /// - CreateType = EMDEventType.x_eet_Create_Type, - // Alter_Type = EMDEventType.x_eet_Alter_Type, - /// - DropType = EMDEventType.x_eet_Drop_Type, - /// - CreateAssembly = EMDEventType.x_eet_Create_Assembly, - /// - AlterAssembly = EMDEventType.x_eet_Alter_Assembly, - /// - DropAssembly = EMDEventType.x_eet_Drop_Assembly, - /// - CreateUser = EMDEventType.x_eet_Create_User, - /// - AlterUser = EMDEventType.x_eet_Alter_User, - /// - DropUser = EMDEventType.x_eet_Drop_User, - /// - CreateRole = EMDEventType.x_eet_Create_Role, - /// - AlterRole = EMDEventType.x_eet_Alter_Role, - /// - DropRole = EMDEventType.x_eet_Drop_Role, - /// - CreateAppRole = EMDEventType.x_eet_Create_AppRole, - /// - AlterAppRole = EMDEventType.x_eet_Alter_AppRole, - /// - DropAppRole = EMDEventType.x_eet_Drop_AppRole, - /// - CreateSchema = EMDEventType.x_eet_Create_Schema, - /// - AlterSchema = EMDEventType.x_eet_Alter_Schema, - /// - DropSchema = EMDEventType.x_eet_Drop_Schema, - /// - CreateLogin = EMDEventType.x_eet_Create_Login, - /// - AlterLogin = EMDEventType.x_eet_Alter_Login, - /// - DropLogin = EMDEventType.x_eet_Drop_Login, - /// - CreateMsgType = EMDEventType.x_eet_Create_MsgType, - /// - DropMsgType = EMDEventType.x_eet_Drop_MsgType, - /// - CreateContract = EMDEventType.x_eet_Create_Contract, - /// - DropContract = EMDEventType.x_eet_Drop_Contract, - /// - CreateQueue = EMDEventType.x_eet_Create_Queue, - /// - AlterQueue = EMDEventType.x_eet_Alter_Queue, - /// - DropQueue = EMDEventType.x_eet_Drop_Queue, - /// - CreateService = EMDEventType.x_eet_Create_Service, - /// - AlterService = EMDEventType.x_eet_Alter_Service, - /// - DropService = EMDEventType.x_eet_Drop_Service, - /// - CreateRoute = EMDEventType.x_eet_Create_Route, - /// - AlterRoute = EMDEventType.x_eet_Alter_Route, - /// - DropRoute = EMDEventType.x_eet_Drop_Route, - /// - GrantStatement = EMDEventType.x_eet_Grant_Statement, - /// - DenyStatement = EMDEventType.x_eet_Deny_Statement, - /// - RevokeStatement = EMDEventType.x_eet_Revoke_Statement, - /// - GrantObject = EMDEventType.x_eet_Grant_Object, - /// - DenyObject = EMDEventType.x_eet_Deny_Object, - /// - RevokeObject = EMDEventType.x_eet_Revoke_Object, - /// - CreateBinding = EMDEventType.x_eet_Create_Binding, - /// - AlterBinding = EMDEventType.x_eet_Alter_Binding, - /// - DropBinding = EMDEventType.x_eet_Drop_Binding, - /// - CreatePartitionFunction = EMDEventType.x_eet_Create_Partition_Function, - /// - AlterPartitionFunction = EMDEventType.x_eet_Alter_Partition_Function, - /// - DropPartitionFunction = EMDEventType.x_eet_Drop_Partition_Function, - /// - CreatePartitionScheme = EMDEventType.x_eet_Create_Partition_Scheme, - /// - AlterPartitionScheme = EMDEventType.x_eet_Alter_Partition_Scheme, - /// - DropPartitionScheme = EMDEventType.x_eet_Drop_Partition_Scheme, - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBuffer.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBuffer.netfx.cs deleted file mode 100644 index 2fc89dfde1..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBuffer.netfx.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Diagnostics; - -namespace Microsoft.Data.SqlClient -{ - internal sealed partial class SqlBuffer - { - internal void SetToDate(DateTime date) - { - Debug.Assert(IsEmpty, "setting value a second time?"); - - _type = StorageType.Date; - _value._int32 = date.Subtract(DateTime.MinValue).Days; - _isNull = false; - } - - internal void SetToDateTime2(DateTime dateTime, byte scale) - { - Debug.Assert(IsEmpty, "setting value a second time?"); - - _type = StorageType.DateTime2; - _value._dateTime2Info._timeInfo._ticks = dateTime.TimeOfDay.Ticks; - _value._dateTime2Info._timeInfo._scale = scale; - _value._dateTime2Info._date = dateTime.Subtract(DateTime.MinValue).Days; - _isNull = false; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs deleted file mode 100644 index ac9e1a79c7..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ /dev/null @@ -1,3230 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Data; -using System.Data.Common; -using System.Data.SqlTypes; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using Microsoft.Data.Common; - -namespace Microsoft.Data.SqlClient -{ - // This internal class helps us to associate the metadata from the target. - // with ColumnOrdinals from the source. - internal sealed class _ColumnMapping - { - internal readonly int _sourceColumnOrdinal; - internal readonly _SqlMetaData _metadata; - - internal _ColumnMapping(int columnId, _SqlMetaData metadata) - { - _sourceColumnOrdinal = columnId; - _metadata = metadata; - } - } - - internal sealed class Row - { - private readonly object[] _dataFields; - - internal Row(int rowCount) - { - _dataFields = new object[rowCount]; - } - - internal object[] DataFields => _dataFields; - - internal object this[int index] => _dataFields[index]; - } - - // The controlling class for one result (metadata + rows) - internal sealed class Result - { - private readonly _SqlMetaDataSet _metadata; - private readonly List _rowset; - - internal Result(_SqlMetaDataSet metadata) - { - _metadata = metadata; - _rowset = new List(); - } - - internal int Count => _rowset.Count; - - internal _SqlMetaDataSet MetaData => _metadata; - - internal Row this[int index] => _rowset[index]; - - internal void AddRow(Row row) => _rowset.Add(row); - } - - // A wrapper object for metadata and rowsets returned by our initial queries - internal sealed class BulkCopySimpleResultSet - { - private readonly List _results; // The list of results - private Result _resultSet; // The current result - private int[] _indexmap; // Associates columnids with indexes in the rowarray - - internal BulkCopySimpleResultSet() - { - _results = new List(); - } - - internal Result this[int idx] => _results[idx]; - - // Callback function for the tdsparser - // (note that setting the metadata adds a resultset) - internal void SetMetaData(_SqlMetaDataSet metadata) - { - _resultSet = new Result(metadata); - _results.Add(_resultSet); - - _indexmap = new int[_resultSet.MetaData.Length]; - for (int i = 0; i < _indexmap.Length; i++) - { - _indexmap[i] = i; - } - } - - // Callback function for the tdsparser. - // This will create an indexmap for the active resultset. - internal int[] CreateIndexMap() => _indexmap; - - // Callback function for the tdsparser. - // This will return an array of rows to store the rowdata. - internal object[] CreateRowBuffer() - { - Row row = new Row(_resultSet.MetaData.Length); - _resultSet.AddRow(row); - return row.DataFields; - } - } - - /// - public sealed class SqlBulkCopy : IDisposable - { - private enum TableNameComponents - { - Server = 0, - Catalog, - Owner, - TableName, - } - - private enum ValueSourceType - { - Unspecified = 0, - IDataReader, - DataTable, - RowArray, - DbDataReader - } - - // Enum for specifying SqlDataReader.Get method used - private enum ValueMethod : byte - { - GetValue, - SqlTypeSqlDecimal, - SqlTypeSqlDouble, - SqlTypeSqlSingle, - DataFeedStream, - DataFeedText, - DataFeedXml - } - - // Used to hold column metadata for SqlDataReader case - private readonly struct SourceColumnMetadata - { - public SourceColumnMetadata(ValueMethod method, bool isSqlType, bool isDataFeed) - { - Method = method; - IsSqlType = isSqlType; - IsDataFeed = isDataFeed; - } - - public readonly ValueMethod Method; - public readonly bool IsSqlType; - public readonly bool IsDataFeed; - } - - // The initial query will return three tables. - // Transaction count has only one value in one column and one row - // MetaData has n columns but no rows - // Collation has 4 columns and n rows - - private const int TranCountResultId = 0; - private const int TranCountRowId = 0; - private const int TranCountValueId = 0; - - private const int MetaDataResultId = 1; - - private const int CollationResultId = 2; - private const int ColIdId = 0; - private const int NameId = 1; - private const int Tds_CollationId = 2; - private const int CollationId = 3; - - private const int MAX_LENGTH = 0x7FFFFFFF; - - private const int DefaultCommandTimeout = 30; - - /// - public event SqlRowsCopiedEventHandler SqlRowsCopied; - - private bool _enableStreaming = false; - private int _batchSize; - private readonly bool _ownConnection; - private readonly SqlBulkCopyOptions _copyOptions; - private int _timeout = DefaultCommandTimeout; - private string _destinationTableName; - private long _rowsCopied; - private int _notifyAfter; - private int _rowsUntilNotification; - private bool _insideRowsCopiedEvent; - - private object _rowSource; - private SqlDataReader _sqlDataReaderRowSource; - private bool _rowSourceIsSqlDataReaderSmi; - private DbDataReader _dbDataReaderRowSource; - private DataTable _dataTableSource; - - private SqlBulkCopyColumnMappingCollection _columnMappings; - private SqlBulkCopyColumnMappingCollection _localColumnMappings; - - private SqlConnection _connection; - private SqlTransaction _internalTransaction; - private SqlTransaction _externalTransaction; - - private ValueSourceType _rowSourceType = ValueSourceType.Unspecified; - private DataRow _currentRow; - private int _currentRowLength; - private DataRowState _rowStateToSkip; - private IEnumerator _rowEnumerator; - - private int RowNumber - { - get - { - int rowNo; - - switch (_rowSourceType) - { - case ValueSourceType.RowArray: - rowNo = ((DataTable)_dataTableSource).Rows.IndexOf(_rowEnumerator.Current as DataRow); - break; - case ValueSourceType.DataTable: - rowNo = ((DataTable)_rowSource).Rows.IndexOf(_rowEnumerator.Current as DataRow); - break; - case ValueSourceType.DbDataReader: - case ValueSourceType.IDataReader: - case ValueSourceType.Unspecified: - default: - return -1; - } - return ++rowNo; - } - } - - private TdsParser _parser; - private TdsParserStateObject _stateObj; - private List<_ColumnMapping> _sortedColumnMappings; - - private static int s_objectTypeCount; // EventSource Counter - internal readonly int _objectID = Interlocked.Increment(ref s_objectTypeCount); - - // Newly added member variables for Async modification, m = member variable to bcp. - private int _savedBatchSize = 0; // Save the batchsize so that changes are not affected unexpectedly. - private bool _hasMoreRowToCopy = false; - private bool _isAsyncBulkCopy = false; - private bool _isBulkCopyingInProgress = false; - private SqlInternalConnectionTds.SyncAsyncLock _parserLock = null; - - private SourceColumnMetadata[] _currentRowMetadata; - -#if DEBUG - internal static bool s_setAlwaysTaskOnWrite; //when set and in DEBUG mode, TdsParser::WriteBulkCopyValue will always return a task - internal static bool SetAlwaysTaskOnWrite - { - set => s_setAlwaysTaskOnWrite = value; - get => s_setAlwaysTaskOnWrite; - } -#endif - - /// - public SqlBulkCopy(SqlConnection connection) - { - if (connection == null) - { - throw ADP.ArgumentNull(nameof(connection)); - } - _connection = connection; - _columnMappings = new SqlBulkCopyColumnMappingCollection(); - ColumnOrderHints = new SqlBulkCopyColumnOrderHintCollection(); - } - - /// - public SqlBulkCopy(SqlConnection connection, SqlBulkCopyOptions copyOptions, SqlTransaction externalTransaction) - : this(connection) - { - _copyOptions = copyOptions; - if (externalTransaction != null && IsCopyOption(SqlBulkCopyOptions.UseInternalTransaction)) - { - throw SQL.BulkLoadConflictingTransactionOption(); - } - - if (!IsCopyOption(SqlBulkCopyOptions.UseInternalTransaction)) - { - _externalTransaction = externalTransaction; - } - } - - /// - public SqlBulkCopy(string connectionString) - { - if (connectionString == null) - { - throw ADP.ArgumentNull(nameof(connectionString)); - } - _connection = new SqlConnection(connectionString); - _columnMappings = new SqlBulkCopyColumnMappingCollection(); - ColumnOrderHints = new SqlBulkCopyColumnOrderHintCollection(); - _ownConnection = true; - } - - /// - public SqlBulkCopy(string connectionString, SqlBulkCopyOptions copyOptions) - : this(connectionString) - { - _copyOptions = copyOptions; - } - - /// - public int BatchSize - { - get => _batchSize; - set - { - if (value >= 0) - { - _batchSize = value; - } - else - { - throw ADP.ArgumentOutOfRange(nameof(BatchSize)); - } - } - } - - /// - public int BulkCopyTimeout - { - get => _timeout; - set - { - if (value < 0) - { - throw SQL.BulkLoadInvalidTimeout(value); - } - _timeout = value; - } - } - - /// - public bool EnableStreaming - { - get => _enableStreaming; - set => _enableStreaming = value; - } - - /// - public SqlBulkCopyColumnMappingCollection ColumnMappings => _columnMappings; - - /// - public SqlBulkCopyColumnOrderHintCollection ColumnOrderHints - { - get; - } - - /// - public string DestinationTableName - { - get => _destinationTableName; - set - { - if (value == null) - { - throw ADP.ArgumentNull(nameof(DestinationTableName)); - } - else if (value.Length == 0) - { - throw ADP.ArgumentOutOfRange(nameof(DestinationTableName)); - } - _destinationTableName = value; - } - } - - /// - public int NotifyAfter - { - get => _notifyAfter; - set - { - if (value >= 0) - { - _notifyAfter = value; - } - else - { - throw ADP.ArgumentOutOfRange(nameof(NotifyAfter)); - } - } - } - - internal int ObjectID => _objectID; - - /// - public int RowsCopied => unchecked((int)_rowsCopied); - - /// - public long RowsCopied64 => _rowsCopied; - - internal SqlStatistics Statistics - { - get - { - if (_connection != null) - { - if (_connection.StatisticsEnabled) - { - return _connection.Statistics; - } - } - return null; - } - } - - void IDisposable.Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private bool IsCopyOption(SqlBulkCopyOptions copyOption) => ((_copyOptions & copyOption) == copyOption); - - //Creates the initial query string, but does not execute it. - private string CreateInitialQuery() - { - string[] parts; - try - { - parts = MultipartIdentifier.ParseMultipartIdentifier(DestinationTableName, "[\"", "]\"", Strings.SQL_BulkCopyDestinationTableName, true); - } - catch (Exception e) - { - throw SQL.BulkLoadInvalidDestinationTable(DestinationTableName, e); - } - if (ADP.IsEmpty(parts[MultipartIdentifier.TableIndex])) - { - throw SQL.BulkLoadInvalidDestinationTable(DestinationTableName, null); - } - string TDSCommand; - - TDSCommand = "select @@trancount; SET FMTONLY ON select * from " + ADP.BuildMultiPartName(parts) + " SET FMTONLY OFF "; - if (_connection.Is2000) - { - // If its a temp DB then try to connect - - string TableCollationsStoredProc; - if (_connection.Is2008OrNewer) - { - TableCollationsStoredProc = "sp_tablecollations_100"; - } - else if (_connection.Is2005OrNewer) - { - TableCollationsStoredProc = "sp_tablecollations_90"; - } - else - { - TableCollationsStoredProc = "sp_tablecollations"; - } - - string TableName = parts[MultipartIdentifier.TableIndex]; - bool isTempTable = TableName.Length > 0 && '#' == TableName[0]; - if (!ADP.IsEmpty(TableName)) - { - // Escape table name to be put inside TSQL literal block (within N''). - TableName = SqlServerEscapeHelper.EscapeStringAsLiteral(TableName); - // VSDD 581951 - escape the table name - TableName = SqlServerEscapeHelper.EscapeIdentifier(TableName); - } - - string SchemaName = parts[MultipartIdentifier.SchemaIndex]; - if (!ADP.IsEmpty(SchemaName)) - { - // Escape schema name to be put inside TSQL literal block (within N''). - SchemaName = SqlServerEscapeHelper.EscapeStringAsLiteral(SchemaName); - // VSDD 581951 - escape the schema name - SchemaName = SqlServerEscapeHelper.EscapeIdentifier(SchemaName); - } - - string CatalogName = parts[MultipartIdentifier.CatalogIndex]; - if (isTempTable && ADP.IsEmpty(CatalogName)) - { - TDSCommand += string.Format("exec tempdb..{0} N'{1}.{2}'", - TableCollationsStoredProc, - SchemaName, - TableName - ); - } - else - { - // VSDD 581951 - escape the catalog name - if (!ADP.IsEmpty(CatalogName)) - { - CatalogName = SqlServerEscapeHelper.EscapeIdentifier(CatalogName); - } - TDSCommand += string.Format("exec {0}..{1} N'{2}.{3}'", - CatalogName, - TableCollationsStoredProc, - SchemaName, - TableName - ); - } - } - return TDSCommand; - } - - // Creates and then executes initial query to get information about the targettable - // When __isAsyncBulkCopy == false (i.e. it is Sync copy): out result contains the resulset. Returns null. - // When __isAsyncBulkCopy == true (i.e. it is Async copy): This still uses the _parser.Run method synchronously and return Task. - // We need to have a _parser.RunAsync to make it real async. - private Task CreateAndExecuteInitialQueryAsync(out BulkCopySimpleResultSet result) - { - string TDSCommand = CreateInitialQuery(); - SqlClientEventSource.Log.TryTraceEvent(" Initial Query: '{0}'", TDSCommand); - SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current); - Task executeTask = _parser.TdsExecuteSQLBatch(TDSCommand, BulkCopyTimeout, null, _stateObj, sync: !_isAsyncBulkCopy, callerHasConnectionLock: true); - - if (executeTask == null) - { - result = new BulkCopySimpleResultSet(); - RunParser(result); - return null; - } - else - { - Debug.Assert(_isAsyncBulkCopy, "Execution pended when not doing async bulk copy"); - result = null; - return executeTask.ContinueWith(t => - { - Debug.Assert(!t.IsCanceled, "Execution task was canceled"); - if (t.IsFaulted) - { - throw t.Exception.InnerException; - } - else - { - var internalResult = new BulkCopySimpleResultSet(); - RunParserReliably(internalResult); - return internalResult; - } - }, TaskScheduler.Default); - } - } - - // Matches associated columns with metadata from initial query. - // Builds and executes the update bulk command. - private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet internalResults) - { - Debug.Assert(internalResults != null, "Where are the results from the initial query?"); - - StringBuilder updateBulkCommandText = new StringBuilder(); - - if (_connection.Is2000 && 0 == internalResults[CollationResultId].Count) - { - throw SQL.BulkLoadNoCollation(); - } - - string[] parts = MultipartIdentifier.ParseMultipartIdentifier(DestinationTableName, "[\"", "]\"", Strings.SQL_BulkCopyDestinationTableName, true); - updateBulkCommandText.AppendFormat("insert bulk {0} (", ADP.BuildMultiPartName(parts)); - int nmatched = 0; // Number of columns that match and are accepted - int nrejected = 0; // Number of columns that match but were rejected - bool rejectColumn; // True if a column is rejected because of an excluded type - - bool isInTransaction; - - if (_parser.Is2005OrNewer) - { - isInTransaction = _connection.HasLocalTransaction; - } - else - { - isInTransaction = (bool)(0 < (SqlInt32)(internalResults[TranCountResultId][TranCountRowId][TranCountValueId])); - } - - // Throw if there is a transaction but no flag is set - if (isInTransaction && - _externalTransaction == null && - _internalTransaction == null && - (_connection.Parser != null && _connection.Parser.CurrentTransaction != null && _connection.Parser.CurrentTransaction.IsLocal)) - { - throw SQL.BulkLoadExistingTransaction(); - } - - HashSet destColumnNames = new HashSet(); - - // Loop over the metadata for each column - _SqlMetaDataSet metaDataSet = internalResults[MetaDataResultId].MetaData; - _sortedColumnMappings = new List<_ColumnMapping>(metaDataSet.Length); - for (int i = 0; i < metaDataSet.Length; i++) - { - _SqlMetaData metadata = metaDataSet[i]; - rejectColumn = false; - - // Check for excluded types - if ((metadata.type == SqlDbType.Timestamp) - || ((metadata.IsIdentity) && !IsCopyOption(SqlBulkCopyOptions.KeepIdentity))) - { - // Remove metadata for excluded columns - metaDataSet[i] = null; - rejectColumn = true; - // We still need to find a matching column association - } - - // Find out if this column is associated - int assocId; - for (assocId = 0; assocId < _localColumnMappings.Count; assocId++) - { - if ((_localColumnMappings[assocId]._destinationColumnOrdinal == metadata.ordinal) || - (UnquotedName(_localColumnMappings[assocId]._destinationColumnName) == metadata.column)) - { - if (rejectColumn) - { - nrejected++; // Count matched columns only - break; - } - - _sortedColumnMappings.Add(new _ColumnMapping(_localColumnMappings[assocId]._internalSourceColumnOrdinal, metadata)); - destColumnNames.Add(metadata.column); - nmatched++; - - if (nmatched > 1) - { - updateBulkCommandText.Append(", "); // A leading comma for all but the first one - } - - // Some datatypes need special handling ... - if (metadata.type == SqlDbType.Variant) - { - AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "sql_variant"); - } - else if (metadata.type == SqlDbType.Udt) - { - AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "varbinary"); - } - else if (metadata.type == SqlDbTypeExtensions.Json) - { - AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "json"); - } - else - { - AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, typeof(SqlDbType).GetEnumName(metadata.type)); - } - - switch (metadata.metaType.NullableType) - { - case TdsEnums.SQLNUMERICN: - case TdsEnums.SQLDECIMALN: - // Decimal and numeric need to include precision and scale - updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0},{1})", metadata.precision, metadata.scale); - break; - case TdsEnums.SQLUDT: - { - if (metadata.IsLargeUdt) - { - updateBulkCommandText.Append("(max)"); - } - else - { - int size = metadata.length; - updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", size); - } - break; - } - case TdsEnums.SQLTIME: - case TdsEnums.SQLDATETIME2: - case TdsEnums.SQLDATETIMEOFFSET: - // date, dateime2, and datetimeoffset need to include scale - updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", metadata.scale); - break; - default: - { - // For non-long non-fixed types we need to add the Size - if (!metadata.metaType.IsFixed && !metadata.metaType.IsLong) - { - int size = metadata.length; - switch (metadata.metaType.NullableType) - { - case TdsEnums.SQLNCHAR: - case TdsEnums.SQLNVARCHAR: - case TdsEnums.SQLNTEXT: - size /= 2; - break; - default: - break; - } - updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", size); - } - else if (metadata.metaType.IsPlp && metadata.metaType.SqlDbType != SqlDbType.Xml && metadata.metaType.SqlDbType != SqlDbTypeExtensions.Json) - { - // Partial length column prefix (max) - updateBulkCommandText.Append("(max)"); - } - break; - } - } - - if (_connection.Is2000) - { - // 2000 or above! - // get collation for column i - - Result rowset = internalResults[CollationResultId]; - object rowvalue = rowset[i][CollationId]; - - bool shouldSendCollation; - switch (metadata.type) - { - case SqlDbType.Char: - case SqlDbType.NChar: - case SqlDbType.VarChar: - case SqlDbType.NVarChar: - case SqlDbType.Text: - case SqlDbType.NText: - shouldSendCollation = true; - break; - - default: - shouldSendCollation = false; - break; - } - - if (rowvalue != null && shouldSendCollation) - { - Debug.Assert(rowvalue is SqlString); - SqlString collation_name = (SqlString)rowvalue; - - if (!collation_name.IsNull) - { - updateBulkCommandText.Append(" COLLATE " + collation_name.Value); - // VSTFDEVDIV 461426: compare collations only if the collation value was set on the metadata - if (_sqlDataReaderRowSource != null && metadata.collation != null) - { - // On SqlDataReader we can verify the sourcecolumn collation! - int sourceColumnId = _localColumnMappings[assocId]._internalSourceColumnOrdinal; - int destinationLcid = metadata.collation.LCID; - int sourceLcid = _sqlDataReaderRowSource.GetLocaleId(sourceColumnId); - if (sourceLcid != destinationLcid) - { - throw SQL.BulkLoadLcidMismatch(sourceLcid, _sqlDataReaderRowSource.GetName(sourceColumnId), destinationLcid, metadata.column); - } - } - } - } - } - break; - } - } - if (assocId == _localColumnMappings.Count) - { - // Remove metadata for unmatched columns - metaDataSet[i] = null; - } - } - - // All columnmappings should have matched up - if (nmatched + nrejected != _localColumnMappings.Count) - { - throw (SQL.BulkLoadNonMatchingColumnMapping()); - } - - updateBulkCommandText.Append(")"); - - if (((_copyOptions & ( - SqlBulkCopyOptions.KeepNulls - | SqlBulkCopyOptions.TableLock - | SqlBulkCopyOptions.CheckConstraints - | SqlBulkCopyOptions.FireTriggers - | SqlBulkCopyOptions.AllowEncryptedValueModifications)) != SqlBulkCopyOptions.Default) - || ColumnOrderHints.Count > 0) - { - bool addSeparator = false; // Insert a comma character if multiple options in list - updateBulkCommandText.Append(" with ("); - if (IsCopyOption(SqlBulkCopyOptions.KeepNulls)) - { - updateBulkCommandText.Append("KEEP_NULLS"); - addSeparator = true; - } - if (IsCopyOption(SqlBulkCopyOptions.TableLock)) - { - updateBulkCommandText.Append((addSeparator ? ", " : "") + "TABLOCK"); - addSeparator = true; - } - if (IsCopyOption(SqlBulkCopyOptions.CheckConstraints)) - { - updateBulkCommandText.Append((addSeparator ? ", " : "") + "CHECK_CONSTRAINTS"); - addSeparator = true; - } - if (IsCopyOption(SqlBulkCopyOptions.FireTriggers)) - { - updateBulkCommandText.Append((addSeparator ? ", " : "") + "FIRE_TRIGGERS"); - addSeparator = true; - } - if (IsCopyOption(SqlBulkCopyOptions.AllowEncryptedValueModifications)) - { - updateBulkCommandText.Append((addSeparator ? ", " : "") + "ALLOW_ENCRYPTED_VALUE_MODIFICATIONS"); - addSeparator = true; - } - if (ColumnOrderHints.Count > 0) - { - updateBulkCommandText.Append((addSeparator ? ", " : "") + TryGetOrderHintText(destColumnNames)); - } - updateBulkCommandText.Append(")"); - } - return (updateBulkCommandText.ToString()); - } - - private string TryGetOrderHintText(HashSet destColumnNames) - { - StringBuilder orderHintText = new StringBuilder("ORDER("); - - foreach (SqlBulkCopyColumnOrderHint orderHint in ColumnOrderHints) - { - string columnNameArg = orderHint.Column; - if (!destColumnNames.Contains(columnNameArg)) - { - // column is not valid in the destination table - throw SQL.BulkLoadOrderHintInvalidColumn(columnNameArg); - } - if (!string.IsNullOrEmpty(columnNameArg)) - { - string columnNameEscaped = SqlServerEscapeHelper.EscapeIdentifier(SqlServerEscapeHelper.EscapeStringAsLiteral(columnNameArg)); - string sortOrderText = orderHint.SortOrder == SortOrder.Descending ? "DESC" : "ASC"; - orderHintText.Append($"{columnNameEscaped} {sortOrderText}, "); - } - } - - orderHintText.Length -= 2; - orderHintText.Append(")"); - return orderHintText.ToString(); - } - - private Task SubmitUpdateBulkCommand(string TDSCommand) - { - SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID{0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current); - Task executeTask = _parser.TdsExecuteSQLBatch(TDSCommand, BulkCopyTimeout, null, _stateObj, sync: !_isAsyncBulkCopy, callerHasConnectionLock: true); - - if (executeTask == null) - { - RunParser(); - return null; - } - else - { - Debug.Assert(_isAsyncBulkCopy, "Execution pended when not doing async bulk copy"); - return executeTask.ContinueWith(t => - { - Debug.Assert(!t.IsCanceled, "Execution task was canceled"); - if (t.IsFaulted) - { - throw t.Exception.InnerException; - } - else - { - RunParserReliably(); - } - }, TaskScheduler.Default); - } - } - - // Starts writing the Bulkcopy data stream - private void WriteMetaData(BulkCopySimpleResultSet internalResults) - { - _stateObj.SetTimeoutSeconds(BulkCopyTimeout); - - _SqlMetaDataSet metadataCollection = internalResults[MetaDataResultId].MetaData; - _stateObj._outputMessageType = TdsEnums.MT_BULK; - _parser.WriteBulkCopyMetaData(metadataCollection, _sortedColumnMappings.Count, _stateObj); - } - - // Terminates the bulk copy operation. - // Must be called at the end of the bulk copy session. - /// - public void Close() - { - if (_insideRowsCopiedEvent) - { - throw SQL.InvalidOperationInsideEvent(); - } - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (disposing) - { - // Dispose dependent objects - _columnMappings = null; - _parser = null; - try - { - // Just in case there is a lingering transaction (which there shouldn't be) - try - { - Debug.Assert(_internalTransaction == null, "Internal transaction exists during dispose"); - if (_internalTransaction != null) - { - _internalTransaction.Rollback(); - _internalTransaction.Dispose(); - _internalTransaction = null; - } - } - catch (Exception e) - { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - ADP.TraceExceptionWithoutRethrow(e); - } - } - finally - { - if (_connection != null) - { - if (_ownConnection) - { - _connection.Dispose(); - } - _connection = null; - } - } - } - } - - // Unified method to read a value from the current row - private object GetValueFromSourceRow(int destRowIndex, out bool isSqlType, out bool isDataFeed, out bool isNull) - { - _SqlMetaData metadata = _sortedColumnMappings[destRowIndex]._metadata; - int sourceOrdinal = _sortedColumnMappings[destRowIndex]._sourceColumnOrdinal; - - switch (_rowSourceType) - { - case ValueSourceType.IDataReader: - case ValueSourceType.DbDataReader: - // Handle data feeds (common for both DbDataReader and SqlDataReader) - if (_currentRowMetadata[destRowIndex].IsDataFeed) - { - if (_dbDataReaderRowSource.IsDBNull(sourceOrdinal)) - { - isSqlType = false; - isDataFeed = false; - isNull = true; - return DBNull.Value; - } - else - { - isSqlType = false; - isDataFeed = true; - isNull = false; - switch (_currentRowMetadata[destRowIndex].Method) - { - case ValueMethod.DataFeedStream: - return new StreamDataFeed(_dbDataReaderRowSource.GetStream(sourceOrdinal)); - case ValueMethod.DataFeedText: - return new TextDataFeed(_dbDataReaderRowSource.GetTextReader(sourceOrdinal)); - case ValueMethod.DataFeedXml: - // Only SqlDataReader supports an XmlReader - // There is no GetXmlReader on DbDataReader, however if GetValue returns XmlReader we will read it as stream if it is assigned to XML field - Debug.Assert(_sqlDataReaderRowSource != null, "Should not be reading row as an XmlReader if bulk copy source is not a SqlDataReader"); - return new XmlDataFeed(_sqlDataReaderRowSource.GetXmlReader(sourceOrdinal)); - default: - Debug.Fail($"Current column is marked as being a DataFeed, but no DataFeed compatible method was provided. Method: {_currentRowMetadata[destRowIndex].Method}"); - isDataFeed = false; - object columnValue = _dbDataReaderRowSource.GetValue(sourceOrdinal); - ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); - return columnValue; - } - } - } - // SqlDataReader-specific logic - else if (_sqlDataReaderRowSource != null) - { - if (_currentRowMetadata[destRowIndex].IsSqlType) - { - INullable value; - isSqlType = true; - isDataFeed = false; - switch (_currentRowMetadata[destRowIndex].Method) - { - case ValueMethod.SqlTypeSqlDecimal: - value = _sqlDataReaderRowSource.GetSqlDecimal(sourceOrdinal); - break; - case ValueMethod.SqlTypeSqlDouble: - // use cast to handle IsNull correctly because no public constructor allows it - value = (SqlDecimal)_sqlDataReaderRowSource.GetSqlDouble(sourceOrdinal); - break; - case ValueMethod.SqlTypeSqlSingle: - // use cast to handle IsNull correctly because no public constructor allows it - value = (SqlDecimal)_sqlDataReaderRowSource.GetSqlSingle(sourceOrdinal); - break; - default: - Debug.Fail($"Current column is marked as being a SqlType, but no SqlType compatible method was provided. Method: {_currentRowMetadata[destRowIndex].Method}"); - value = (INullable)_sqlDataReaderRowSource.GetSqlValue(sourceOrdinal); - break; - } - - isNull = value.IsNull; - return value; - } - else - { - isSqlType = false; - isDataFeed = false; - - object value = _sqlDataReaderRowSource.GetValue(sourceOrdinal); - isNull = ((value == null) || (value == DBNull.Value)); - if ((!isNull) && (metadata.type == SqlDbType.Udt)) - { - var columnAsINullable = value as INullable; - isNull = (columnAsINullable != null) && columnAsINullable.IsNull; - } -#if DEBUG - else if (!isNull) - { - Debug.Assert(!(value is INullable) || !((INullable)value).IsNull, "IsDBNull returned false, but GetValue returned a null INullable"); - } -#endif - return value; - } - } - else - { - isDataFeed = false; - - IDataReader rowSourceAsIDataReader = (IDataReader)_rowSource; - - // Only use IsDbNull when streaming is enabled and only for non-SqlDataReader - if ((_enableStreaming) && (_sqlDataReaderRowSource == null) && (rowSourceAsIDataReader.IsDBNull(sourceOrdinal))) - { - isSqlType = false; - isNull = true; - return DBNull.Value; - } - else - { - object columnValue = rowSourceAsIDataReader.GetValue(sourceOrdinal); - ADP.IsNullOrSqlType(columnValue, out isNull, out isSqlType); - return columnValue; - } - } - - case ValueSourceType.DataTable: - case ValueSourceType.RowArray: - { - Debug.Assert(_currentRow != null, "uninitialized _currentRow"); - Debug.Assert(sourceOrdinal < _currentRowLength, "inconsistency of length of rows from rowsource!"); - - isDataFeed = false; - object currentRowValue = _currentRow[sourceOrdinal]; - ADP.IsNullOrSqlType(currentRowValue, out isNull, out isSqlType); - - // If this row is not null, and there are special storage types for this row, then handle the special storage types - if ((!isNull) && (_currentRowMetadata[destRowIndex].IsSqlType)) - { - switch (_currentRowMetadata[destRowIndex].Method) - { - case ValueMethod.SqlTypeSqlSingle: - { - if (isSqlType) - { - return new SqlDecimal(((SqlSingle)currentRowValue).Value); - } - else - { - float f = (float)currentRowValue; - if (!float.IsNaN(f)) - { - isSqlType = true; - return new SqlDecimal(f); - } - break; - } - } - case ValueMethod.SqlTypeSqlDouble: - { - if (isSqlType) - { - return new SqlDecimal(((SqlDouble)currentRowValue).Value); - } - else - { - double d = (double)currentRowValue; - if (!double.IsNaN(d)) - { - isSqlType = true; - return new SqlDecimal(d); - } - break; - } - } - case ValueMethod.SqlTypeSqlDecimal: - { - if (isSqlType) - { - return (SqlDecimal)currentRowValue; - } - else - { - isSqlType = true; - return new SqlDecimal((decimal)currentRowValue); - } - } - default: - { - Debug.Fail($"Current column is marked as being a SqlType, but no SqlType compatible method was provided. Method: {_currentRowMetadata[destRowIndex].Method}"); - break; - } - } - } - - // If we are here then either the value is null, there was no special storage type for this column or the special storage type wasn't handled (e.g. if the currentRowValue is NaN) - return currentRowValue; - } - default: - { - Debug.Fail("ValueSourcType unspecified"); - throw ADP.NotSupported(); - } - } - } - - // Unified method to read a row from the current rowsource. - // When _isAsyncBulkCopy == true (i.e. async copy): returns Task when IDataReader is a DbDataReader, Null for others. - // When _isAsyncBulkCopy == false (i.e. sync copy): returns null. Uses ReadFromRowSource to get the boolean value. - // "more" -- should be used by the caller only when the return value is null. - private Task ReadFromRowSourceAsync(CancellationToken cts) - { - if (_isAsyncBulkCopy && _dbDataReaderRowSource != null) - { - // This will call ReadAsync for DbDataReader (for SqlDataReader it will be truly async read; for non-SqlDataReader it may block.) - return _dbDataReaderRowSource.ReadAsync(cts).ContinueWith( - static (Task task, object state) => - { - if (task.Status == TaskStatus.RanToCompletion) - { - ((SqlBulkCopy)state)._hasMoreRowToCopy = task.Result; - } - return task; - }, - state: this, - scheduler: TaskScheduler.Default - ).Unwrap(); - } - else - { // This will call Read for DataRows, DataTable and IDataReader (this includes all IDataReader except DbDataReader) - // Release lock to prevent possible deadlocks - SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection(); - bool semaphoreLock = internalConnection._parserLock.CanBeReleasedFromAnyThread; - internalConnection._parserLock.Release(); - - _hasMoreRowToCopy = false; - try - { - _hasMoreRowToCopy = ReadFromRowSource(); // Synchronous calls for DataRows and DataTable won't block. For IDataReader, it may block. - } - catch (Exception ex) - { - if (_isAsyncBulkCopy) - { - return Task.FromException(ex); - } - else - { - throw; - } - } - finally - { - internalConnection._parserLock.Wait(canReleaseFromAnyThread: semaphoreLock); - } - return null; - } - } - - private bool ReadFromRowSource() - { - switch (_rowSourceType) - { - case ValueSourceType.DbDataReader: - case ValueSourceType.IDataReader: - return ((IDataReader)_rowSource).Read(); - - // Treatment for RowArray case is same as for DataTable, prevent code duplicate - case ValueSourceType.RowArray: - case ValueSourceType.DataTable: - Debug.Assert(_rowEnumerator != null, "uninitialized _rowEnumerator"); - Debug.Assert((_rowStateToSkip & DataRowState.Deleted) != 0, "Deleted is a permitted rowstate?"); - - // Repeat until we get a row that is not deleted or there are no more rows - do - { - if (!_rowEnumerator.MoveNext()) - { - return false; - } - _currentRow = (DataRow)_rowEnumerator.Current; - } while ((_currentRow.RowState & _rowStateToSkip) != 0); // Repeat if there is an unexpected rowstate - - _currentRowLength = _currentRow.ItemArray.Length; - return true; - - default: - Debug.Fail("ValueSourceType unspecified"); - throw ADP.NotSupported(); - } - } - - private SourceColumnMetadata GetColumnMetadata(int ordinal) - { - int sourceOrdinal = _sortedColumnMappings[ordinal]._sourceColumnOrdinal; - _SqlMetaData metadata = _sortedColumnMappings[ordinal]._metadata; - - // Handle special Sql data types for SqlDataReader and DataTables - ValueMethod method; - bool isSqlType; - bool isDataFeed; - - if (((_sqlDataReaderRowSource != null) || (_dataTableSource != null)) && ((metadata.metaType.NullableType == TdsEnums.SQLDECIMALN) || (metadata.metaType.NullableType == TdsEnums.SQLNUMERICN))) - { - isDataFeed = false; - - Type t; - switch (_rowSourceType) - { - case ValueSourceType.DbDataReader: - case ValueSourceType.IDataReader: - t = _sqlDataReaderRowSource.GetFieldType(sourceOrdinal); - break; - case ValueSourceType.DataTable: - case ValueSourceType.RowArray: - t = _dataTableSource.Columns[sourceOrdinal].DataType; - break; - default: - t = null; - Debug.Fail($"Unknown value source: {_rowSourceType}"); - break; - } - - if (typeof(SqlDecimal) == t || typeof(decimal) == t) - { - isSqlType = true; - method = ValueMethod.SqlTypeSqlDecimal; // Source Type Decimal - } - else if (typeof(SqlDouble) == t || typeof(double) == t) - { - isSqlType = true; - method = ValueMethod.SqlTypeSqlDouble; // Source Type SqlDouble - } - else if (typeof(SqlSingle) == t || typeof(float) == t) - { - isSqlType = true; - method = ValueMethod.SqlTypeSqlSingle; // Source Type SqlSingle - } - else - { - isSqlType = false; - method = ValueMethod.GetValue; - } - } - // Check for data streams - else if ((_enableStreaming) && ((metadata.length == MAX_LENGTH) || metadata.metaType.SqlDbType == SqlDbTypeExtensions.Json) && (!_rowSourceIsSqlDataReaderSmi)) - { - isSqlType = false; - - if (_sqlDataReaderRowSource != null) - { - // MetaData property is not set for SMI, but since streaming is disabled we do not need it - MetaType mtSource = _sqlDataReaderRowSource.MetaData[sourceOrdinal].metaType; - - // There is no memory gain for non-sequential access for binary - if ((metadata.type == SqlDbType.VarBinary) && (mtSource.IsBinType) && (mtSource.SqlDbType != SqlDbType.Timestamp) && _sqlDataReaderRowSource.IsCommandBehavior(CommandBehavior.SequentialAccess)) - { - isDataFeed = true; - method = ValueMethod.DataFeedStream; - } - // For text and XML there is memory gain from streaming on destination side even if reader is non-sequential - else if (((metadata.type == SqlDbType.VarChar) || (metadata.type == SqlDbType.NVarChar) || (metadata.type == SqlDbTypeExtensions.Json)) && (mtSource.IsCharType) && (mtSource.SqlDbType != SqlDbType.Xml)) - { - isDataFeed = true; - method = ValueMethod.DataFeedText; - } - else if ((metadata.type == SqlDbType.Xml) && (mtSource.SqlDbType == SqlDbType.Xml)) - { - isDataFeed = true; - method = ValueMethod.DataFeedXml; - } - else - { - isDataFeed = false; - method = ValueMethod.GetValue; - } - } - else if (_dbDataReaderRowSource != null) - { - if (metadata.type == SqlDbType.VarBinary) - { - isDataFeed = true; - method = ValueMethod.DataFeedStream; - } - else if ((metadata.type == SqlDbType.VarChar) || (metadata.type == SqlDbType.NVarChar)) - { - isDataFeed = true; - method = ValueMethod.DataFeedText; - } - else - { - isDataFeed = false; - method = ValueMethod.GetValue; - } - } - else - { - isDataFeed = false; - method = ValueMethod.GetValue; - } - } - else - { - isSqlType = false; - isDataFeed = false; - method = ValueMethod.GetValue; - } - - return new SourceColumnMetadata(method, isSqlType, isDataFeed); - } - - private void CreateOrValidateConnection(string method) - { - if (_connection == null) - { - throw ADP.ConnectionRequired(method); - } - - if (_connection.IsContextConnection) - { - throw SQL.NotAvailableOnContextConnection(); - } - - if (_ownConnection && _connection.State != ConnectionState.Open) - { - _connection.Open(); - } - - // Close any non-MARS dead readers, if applicable, and then throw if still busy. - _connection.ValidateConnectionForExecute(method, null); - - // If we have a transaction, check to ensure that the active - // connection property matches the connection associated with - // the transaction. - if (_externalTransaction != null && _connection != _externalTransaction.Connection) - { - throw ADP.TransactionConnectionMismatch(); - } - } - - // Runs the _parser until it is done and ensures that ThreadHasParserLockForClose is correctly set and unset - // Ensure that you only call this inside of a Reliability Section - private void RunParser(BulkCopySimpleResultSet bulkCopyHandler = null) - { - // In case of error while reading, we should let the connection know that we already own the _parserLock - SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection(); - - internalConnection.ThreadHasParserLockForClose = true; - try - { - _parser.Run(RunBehavior.UntilDone, null, null, bulkCopyHandler, _stateObj); - } - finally - { - internalConnection.ThreadHasParserLockForClose = false; - } - } - - // Runs the _parser until it is done and ensures that ThreadHasParserLockForClose is correctly set and unset - // This takes care of setting up the Reliability Section, and will doom the connect if there is a catastrophic (OOM, StackOverflow, ThreadAbort) error - private void RunParserReliably(BulkCopySimpleResultSet bulkCopyHandler = null) - { - // In case of error while reading, we should let the connection know that we already own the _parserLock - SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection(); - internalConnection.ThreadHasParserLockForClose = true; - try - { - _parser.RunReliably(RunBehavior.UntilDone, null, null, bulkCopyHandler, _stateObj); - } - finally - { - internalConnection.ThreadHasParserLockForClose = false; - } - } - - private void CommitTransaction() - { - if (_internalTransaction != null) - { - SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection(); - internalConnection.ThreadHasParserLockForClose = true; // In case of error, let the connection know that we have the lock - try - { - _internalTransaction.Commit(); - _internalTransaction.Dispose(); - _internalTransaction = null; - } - finally - { - internalConnection.ThreadHasParserLockForClose = false; - } - } - } - - private void AbortTransaction() - { - if (_internalTransaction != null) - { - if (!_internalTransaction.IsZombied) - { - SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection(); - internalConnection.ThreadHasParserLockForClose = true; // In case of error, let the connection know that we have the lock - try - { - _internalTransaction.Rollback(); - } - finally - { - internalConnection.ThreadHasParserLockForClose = false; - } - } - _internalTransaction.Dispose(); - _internalTransaction = null; - } - } - - // Appends columnname in square brackets, a space, and the typename to the query. - // Putting the name in quotes also requires doubling existing ']' so that they are not mistaken for - // the closing quote. - // example: abc will become [abc] but abc[] will become [abc[]]] - private void AppendColumnNameAndTypeName(StringBuilder query, string columnName, string typeName) - { - SqlServerEscapeHelper.EscapeIdentifier(query, columnName); - query.Append(" "); - query.Append(typeName); - } - - private string UnquotedName(string name) - { - if (ADP.IsEmpty(name)) - return null; - if (name[0] == '[') - { - int l = name.Length; - Debug.Assert(name[l - 1] == ']', "Name starts with [ but does not end with ]"); - name = name.Substring(1, l - 2); - } - return name; - } - - private object ValidateBulkCopyVariant(object value) - { - // From the spec: - // "The only acceptable types are ..." - // GUID, BIGVARBINARY, BIGBINARY, BIGVARCHAR, BIGCHAR, NVARCHAR, NCHAR, BIT, INT1, INT2, INT4, INT8, - // MONEY4, MONEY, DECIMALN, NUMERICN, FTL4, FLT8, DATETIME4 and DATETIME - MetaType metatype = MetaType.GetMetaTypeFromValue(value); - switch (metatype.TDSType) - { - case TdsEnums.SQLFLT4: - case TdsEnums.SQLFLT8: - case TdsEnums.SQLINT8: - case TdsEnums.SQLINT4: - case TdsEnums.SQLINT2: - case TdsEnums.SQLINT1: - case TdsEnums.SQLBIT: - case TdsEnums.SQLBIGVARBINARY: - case TdsEnums.SQLBIGVARCHAR: - case TdsEnums.SQLUNIQUEID: - case TdsEnums.SQLNVARCHAR: - case TdsEnums.SQLDATETIME: - case TdsEnums.SQLMONEY: - case TdsEnums.SQLNUMERICN: - case TdsEnums.SQLDATE: - case TdsEnums.SQLTIME: - case TdsEnums.SQLDATETIME2: - case TdsEnums.SQLDATETIMEOFFSET: - if (value is INullable) - { // Current limitation in the SqlBulkCopy Variant code limits BulkCopy to CLR/COM Types. - return MetaType.GetComValueFromSqlVariant(value); - } - else - { - return value; - } - default: - throw SQL.BulkLoadInvalidVariantValue(); - } - } - - private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, ref bool isSqlType, out bool coercedToDataFeed) - { - coercedToDataFeed = false; - - if (isNull) - { - if (!metadata.IsNullable) - { - throw SQL.BulkLoadBulkLoadNotAllowDBNull(metadata.column); - } - return value; - } - - MetaType type = metadata.metaType; - bool typeChanged = false; - - // If the column is encrypted then we are going to transparently encrypt this column - // (based on connection string setting)- Use the metaType for the underlying - // value (unencrypted value) for conversion/casting purposes (below). - // Note - this flag is set if connection string options has TCE turned on - byte scale = metadata.scale; - byte precision = metadata.precision; - int length = metadata.length; - if (metadata.isEncrypted) - { - Debug.Assert(_parser.ShouldEncryptValuesForBulkCopy()); - type = metadata.baseTI.metaType; - scale = metadata.baseTI.scale; - precision = metadata.baseTI.precision; - length = metadata.baseTI.length; - } - - try - { - MetaType mt; - switch (type.NullableType) - { - case TdsEnums.SQLNUMERICN: - case TdsEnums.SQLDECIMALN: - mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); - value = SqlParameter.CoerceValue(value, mt, out coercedToDataFeed, out typeChanged, false); - - // Convert Source Decimal Precision and Scale to Destination Precision and Scale - // Sql decimal data could get corrupted on insert if the scale of - // the source and destination weren't the same. The BCP protocol, specifies the - // scale of the incoming data in the insert statement, we just tell the server we - // are inserting the same scale back. - SqlDecimal sqlValue; - if ((isSqlType) && (!typeChanged)) - { - sqlValue = (SqlDecimal)value; - } - else - { - sqlValue = new SqlDecimal((decimal)value); - } - - if (sqlValue.Scale != scale) - { - sqlValue = TdsParser.AdjustSqlDecimalScale(sqlValue, scale); - } - - if (sqlValue.Precision > precision) - { - try - { - sqlValue = SqlDecimal.ConvertToPrecScale(sqlValue, precision, sqlValue.Scale); - } - catch (SqlTruncateException) - { - throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), ADP.ParameterValueOutOfRange(sqlValue)); - } - catch (Exception e) - { - throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), e); - } - } - - // Perf: It is more efficient to write a SqlDecimal than a decimal since we need to break it into its 'bits' when writing - value = sqlValue; - isSqlType = true; - typeChanged = false; // Setting this to false as SqlParameter.CoerceValue will only set it to true when converting to a CLR type - break; - - case TdsEnums.SQLINTN: - case TdsEnums.SQLFLTN: - case TdsEnums.SQLFLT4: - case TdsEnums.SQLFLT8: - case TdsEnums.SQLMONEYN: - case TdsEnums.SQLDATETIM4: - case TdsEnums.SQLDATETIME: - case TdsEnums.SQLDATETIMN: - case TdsEnums.SQLBIT: - case TdsEnums.SQLBITN: - case TdsEnums.SQLUNIQUEID: - case TdsEnums.SQLBIGBINARY: - case TdsEnums.SQLBIGVARBINARY: - case TdsEnums.SQLIMAGE: - case TdsEnums.SQLBIGCHAR: - case TdsEnums.SQLBIGVARCHAR: - case TdsEnums.SQLTEXT: - case TdsEnums.SQLDATE: - case TdsEnums.SQLTIME: - case TdsEnums.SQLDATETIME2: - case TdsEnums.SQLDATETIMEOFFSET: - mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); - value = SqlParameter.CoerceValue(value, mt, out coercedToDataFeed, out typeChanged, false); - break; - case TdsEnums.SQLNCHAR: - case TdsEnums.SQLNVARCHAR: - case TdsEnums.SQLNTEXT: - mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); - value = SqlParameter.CoerceValue(value, mt, out coercedToDataFeed, out typeChanged, false); - if (!coercedToDataFeed) - { // We do not need to test for TextDataFeed as it is only assigned to (N)VARCHAR(MAX) - string str = ((isSqlType) && (!typeChanged)) ? ((SqlString)value).Value : ((string)value); - int maxStringLength = length / 2; - if (str.Length > maxStringLength) - { - if (metadata.isEncrypted) - { - str = ""; - } - else - { - // We truncate to at most 100 characters to match SQL Servers behavior as described in - // https://blogs.msdn.microsoft.com/sql_server_team/string-or-binary-data-would-be-truncated-replacing-the-infamous-error-8152/ - str = str.Remove(Math.Min(maxStringLength, 100)); - } - throw SQL.BulkLoadStringTooLong(_destinationTableName, metadata.column, str); - } - } - break; - case TdsEnums.SQLVARIANT: - value = ValidateBulkCopyVariant(value); - typeChanged = true; - break; - case TdsEnums.SQLUDT: - // UDTs are sent as varbinary so we need to get the raw bytes - // unlike other types the parser does not like SQLUDT in form of SqlType - // so we cast to a CLR type. - - // Hack for type system version knob - only call GetBytes if the value is not already - // in byte[] form. - if (!(value is byte[])) - { - value = _connection.GetBytes(value); - typeChanged = true; - } - break; - case TdsEnums.SQLXMLTYPE: - case TdsEnums.SQLJSON: - // Could be either string, SqlCachedBuffer, XmlReader or XmlDataFeed - Debug.Assert((value is XmlReader) || (value is SqlCachedBuffer) || (value is string) || (value is SqlString) || (value is XmlDataFeed), "Invalid value type of Xml datatype"); - if (value is XmlReader) - { - value = new XmlDataFeed((XmlReader)value); - typeChanged = true; - coercedToDataFeed = true; - } - break; - - default: - Debug.Fail("Unknown TdsType!" + type.NullableType.ToString("x2", (IFormatProvider)null)); - throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), null); - } - - if (typeChanged) - { - // All type changes change to CLR types - isSqlType = false; - } - - return value; - } - catch (Exception e) - { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - throw SQL.BulkLoadCannotConvertValue(value.GetType(), type, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), e); - } - } - - /// - public void WriteToServer(DbDataReader reader) - { - SqlConnection.ExecutePermission.Demand(); - - if (reader == null) - { - throw new ArgumentNullException(nameof(reader)); - } - - if (_isBulkCopyingInProgress) - { - throw SQL.BulkLoadPendingOperation(); - } - - SqlStatistics statistics = Statistics; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - ResetWriteToServerGlobalVariables(); - _rowSource = reader; - _dbDataReaderRowSource = reader; - _sqlDataReaderRowSource = reader as SqlDataReader; - - if (_sqlDataReaderRowSource != null) - { - _rowSourceIsSqlDataReaderSmi = _sqlDataReaderRowSource is SqlDataReaderSmi; - } - _rowSourceType = ValueSourceType.DbDataReader; - - WriteRowSourceToServerAsync(reader.FieldCount, CancellationToken.None); //It returns null since _isAsyncBulkCopy = false; - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - - /// - public void WriteToServer(IDataReader reader) - { - SqlConnection.ExecutePermission.Demand(); - - if (reader == null) - { - throw new ArgumentNullException(nameof(reader)); - } - - if (_isBulkCopyingInProgress) - { - throw SQL.BulkLoadPendingOperation(); - } - - SqlStatistics statistics = Statistics; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - ResetWriteToServerGlobalVariables(); - _rowSource = reader; - _sqlDataReaderRowSource = _rowSource as SqlDataReader; - if (_sqlDataReaderRowSource != null) - { - _rowSourceIsSqlDataReaderSmi = _sqlDataReaderRowSource is SqlDataReaderSmi; - } - _dbDataReaderRowSource = _rowSource as DbDataReader; - _rowSourceType = ValueSourceType.IDataReader; - WriteRowSourceToServerAsync(reader.FieldCount, CancellationToken.None); //It returns null since _isAsyncBulkCopy = false; - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - - /// - public void WriteToServer(DataTable table) => WriteToServer(table, 0); - - /// - public void WriteToServer(DataTable table, DataRowState rowState) - { - SqlConnection.ExecutePermission.Demand(); - - if (table == null) - { - throw new ArgumentNullException(nameof(table)); - } - - if (_isBulkCopyingInProgress) - { - throw SQL.BulkLoadPendingOperation(); - } - - SqlStatistics statistics = Statistics; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - ResetWriteToServerGlobalVariables(); - _rowStateToSkip = ((rowState == 0) || (rowState == DataRowState.Deleted)) ? DataRowState.Deleted : ~rowState | DataRowState.Deleted; - _rowSource = table; - _dataTableSource = table; - _rowSourceType = ValueSourceType.DataTable; - _rowEnumerator = table.Rows.GetEnumerator(); - - WriteRowSourceToServerAsync(table.Columns.Count, CancellationToken.None); //It returns null since _isAsyncBulkCopy = false; - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - - /// - public void WriteToServer(DataRow[] rows) - { - SqlConnection.ExecutePermission.Demand(); - - SqlStatistics statistics = Statistics; - - if (rows == null) - { - throw new ArgumentNullException(nameof(rows)); - } - - if (_isBulkCopyingInProgress) - { - throw SQL.BulkLoadPendingOperation(); - } - - if (rows.Length == 0) - { - return; // Nothing to do. user passed us an empty array - } - - try - { - statistics = SqlStatistics.StartTimer(Statistics); - ResetWriteToServerGlobalVariables(); - DataTable table = rows[0].Table; - Debug.Assert(table != null, "How can we have rows without a table?"); - _rowStateToSkip = DataRowState.Deleted; // Don't allow deleted rows - _rowSource = rows; - _dataTableSource = table; - _rowSourceType = ValueSourceType.RowArray; - _rowEnumerator = rows.GetEnumerator(); - - WriteRowSourceToServerAsync(table.Columns.Count, CancellationToken.None); //It returns null since _isAsyncBulkCopy = false; - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - - /// - public Task WriteToServerAsync(DataRow[] rows) => WriteToServerAsync(rows, CancellationToken.None); - - /// - public Task WriteToServerAsync(DataRow[] rows, CancellationToken cancellationToken) - { - Task resultTask = null; - SqlConnection.ExecutePermission.Demand(); - - if (rows == null) - { - throw new ArgumentNullException(nameof(rows)); - } - - if (_isBulkCopyingInProgress) - { - throw SQL.BulkLoadPendingOperation(); - } - - SqlStatistics statistics = Statistics; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - ResetWriteToServerGlobalVariables(); - if (rows.Length == 0) - { - TaskCompletionSource source = new TaskCompletionSource(); - if (cancellationToken.IsCancellationRequested) - { - source.SetCanceled(); - } - else - { - source.SetResult(null); - } - resultTask = source.Task; - return resultTask; // nothing to do. user passed us an empty array. Return a completed Task. - } - - DataTable table = rows[0].Table; - Debug.Assert(table != null, "How can we have rows without a table?"); - _rowStateToSkip = DataRowState.Deleted; // Don't allow deleted rows - _rowSource = rows; - _dataTableSource = table; - _rowSourceType = ValueSourceType.RowArray; - _rowEnumerator = rows.GetEnumerator(); - _isAsyncBulkCopy = true; - resultTask = WriteRowSourceToServerAsync(table.Columns.Count, cancellationToken); // It returns Task since _isAsyncBulkCopy = true; - } - finally - { - SqlStatistics.StopTimer(statistics); - } - return resultTask; - } - - /// - public Task WriteToServerAsync(DbDataReader reader) => WriteToServerAsync(reader, CancellationToken.None); - - /// - public Task WriteToServerAsync(DbDataReader reader, CancellationToken cancellationToken) - { - Task resultTask = null; - SqlConnection.ExecutePermission.Demand(); - - if (reader == null) - { - throw new ArgumentNullException(nameof(reader)); - } - - if (_isBulkCopyingInProgress) - { - throw SQL.BulkLoadPendingOperation(); - } - - SqlStatistics statistics = Statistics; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - ResetWriteToServerGlobalVariables(); - _rowSource = reader; - _sqlDataReaderRowSource = reader as SqlDataReader; - _dbDataReaderRowSource = reader; - _rowSourceType = ValueSourceType.DbDataReader; - _isAsyncBulkCopy = true; - resultTask = WriteRowSourceToServerAsync(reader.FieldCount, cancellationToken); // It returns Task since _isAsyncBulkCopy = true; - } - finally - { - SqlStatistics.StopTimer(statistics); - } - return resultTask; - } - - /// - public Task WriteToServerAsync(IDataReader reader) => WriteToServerAsync(reader, CancellationToken.None); - - /// - public Task WriteToServerAsync(IDataReader reader, CancellationToken cancellationToken) - { - Task resultTask = null; - SqlConnection.ExecutePermission.Demand(); - - if (reader == null) - { - throw new ArgumentNullException(nameof(reader)); - } - - if (_isBulkCopyingInProgress) - { - throw SQL.BulkLoadPendingOperation(); - } - - SqlStatistics statistics = Statistics; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - ResetWriteToServerGlobalVariables(); - _rowSource = reader; - _sqlDataReaderRowSource = _rowSource as SqlDataReader; - _dbDataReaderRowSource = _rowSource as DbDataReader; - _rowSourceType = ValueSourceType.IDataReader; - _isAsyncBulkCopy = true; - resultTask = WriteRowSourceToServerAsync(reader.FieldCount, cancellationToken); // It returns Task since _isAsyncBulkCopy = true; - } - finally - { - SqlStatistics.StopTimer(statistics); - } - return resultTask; - } - - /// - public Task WriteToServerAsync(DataTable table) => WriteToServerAsync(table, 0, CancellationToken.None); - - /// - public Task WriteToServerAsync(DataTable table, CancellationToken cancellationToken) => WriteToServerAsync(table, 0, cancellationToken); - - /// - public Task WriteToServerAsync(DataTable table, DataRowState rowState) => WriteToServerAsync(table, rowState, CancellationToken.None); - - /// - public Task WriteToServerAsync(DataTable table, DataRowState rowState, CancellationToken cancellationToken) - { - Task resultTask = null; - SqlConnection.ExecutePermission.Demand(); - - if (table == null) - { - throw new ArgumentNullException(nameof(table)); - } - - if (_isBulkCopyingInProgress) - { - throw SQL.BulkLoadPendingOperation(); - } - - SqlStatistics statistics = Statistics; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - ResetWriteToServerGlobalVariables(); - _rowStateToSkip = ((rowState == 0) || (rowState == DataRowState.Deleted)) ? DataRowState.Deleted : ~rowState | DataRowState.Deleted; - _rowSource = table; - _dataTableSource = table; - _rowSourceType = ValueSourceType.DataTable; - _rowEnumerator = table.Rows.GetEnumerator(); - _isAsyncBulkCopy = true; - resultTask = WriteRowSourceToServerAsync(table.Columns.Count, cancellationToken); // It returns Task since _isAsyncBulkCopy = true; - } - finally - { - SqlStatistics.StopTimer(statistics); - } - return resultTask; - } - - private Task WriteRowSourceToServerAsync(int columnCount, CancellationToken ctoken) - { - // If user's token is canceled, return a canceled task - if (ctoken.IsCancellationRequested) - { - Debug.Assert(_isAsyncBulkCopy, "Should not have a cancelled token for a synchronous bulk copy"); - return ADP.CreatedTaskWithCancellation(); - } - - Task reconnectTask = _connection._currentReconnectionTask; - if (reconnectTask != null && !reconnectTask.IsCompleted) - { - if (_isAsyncBulkCopy) - { - TaskCompletionSource tcs = new TaskCompletionSource(); - reconnectTask.ContinueWith((t) => - { - Task writeTask = WriteRowSourceToServerAsync(columnCount, ctoken); - if (writeTask == null) - { - tcs.SetResult(null); - } - else - { - AsyncHelper.ContinueTaskWithState(writeTask, tcs, tcs, - onSuccess: static (object state) => ((TaskCompletionSource)state).SetResult(null) - ); - } - }, ctoken); // We do not need to propagate exception, etc, from reconnect task, we just need to wait for it to finish. - return tcs.Task; - } - else - { - AsyncHelper.WaitForCompletion(reconnectTask, BulkCopyTimeout, static () => throw SQL.CR_ReconnectTimeout(), rethrowExceptions: false); - } - } - - bool finishedSynchronously = true; - _isBulkCopyingInProgress = true; - - CreateOrValidateConnection(SQL.WriteToServer); - SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection(); - - Debug.Assert(_parserLock == null, "Previous parser lock not cleaned"); - _parserLock = internalConnection._parserLock; - _parserLock.Wait(canReleaseFromAnyThread: _isAsyncBulkCopy); - - TdsParser bestEffortCleanupTarget = null; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else // !DEBUG - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); - WriteRowSourceToServerCommon(columnCount); //this is common in both sync and async - Task resultTask = WriteToServerInternalAsync(ctoken); // resultTask is null for sync, but Task for async. - if (resultTask != null) - { - finishedSynchronously = false; - return resultTask.ContinueWith( - static (Task task, object state) => - { - SqlBulkCopy sqlBulkCopy = (SqlBulkCopy)state; - try - { - sqlBulkCopy.AbortTransaction(); // if there is one, on success transactions will be commited - } - finally - { - sqlBulkCopy._isBulkCopyingInProgress = false; - if (sqlBulkCopy._parser != null) - { - sqlBulkCopy._parser._asyncWrite = false; - } - if (sqlBulkCopy._parserLock != null) - { - sqlBulkCopy._parserLock.Release(); - sqlBulkCopy._parserLock = null; - } - } - return task; - }, - state: this, - scheduler: TaskScheduler.Default - ).Unwrap(); - } - return null; - } - -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - _connection.Abort(e); - throw; - } - catch (System.StackOverflowException e) - { - _connection.Abort(e); - throw; - } - catch (System.Threading.ThreadAbortException e) - { - _connection.Abort(e); - SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); - throw; - } - finally - { - _columnMappings.ReadOnly = false; - if (finishedSynchronously) - { - try - { - AbortTransaction(); // If there is one, on success transactions will be committed. - } - finally - { - _isBulkCopyingInProgress = false; - if (_parser != null) - { - _parser._asyncWrite = false; - } - if (_parserLock != null) - { - _parserLock.Release(); - _parserLock = null; - } - } - } - } - } - - // Handles the column mapping. - private void WriteRowSourceToServerCommon(int columnCount) - { - bool unspecifiedColumnOrdinals = false; - - _columnMappings.ReadOnly = true; - _localColumnMappings = _columnMappings; - if (_localColumnMappings.Count > 0) - { - _localColumnMappings.ValidateCollection(); - foreach (SqlBulkCopyColumnMapping bulkCopyColumn in _localColumnMappings) - { - if (bulkCopyColumn._internalSourceColumnOrdinal == -1) - { - unspecifiedColumnOrdinals = true; - break; - } - } - } - else - { - _localColumnMappings = new SqlBulkCopyColumnMappingCollection(); - _localColumnMappings.CreateDefaultMapping(columnCount); - } - - // perf: If the user specified all column ordinals we do not need to get a schematable - if (unspecifiedColumnOrdinals) - { - int index = -1; - unspecifiedColumnOrdinals = false; - - // Match up sourceColumn names with sourceColumn ordinals - if (_localColumnMappings.Count > 0) - { - foreach (SqlBulkCopyColumnMapping bulkCopyColumn in _localColumnMappings) - { - if (bulkCopyColumn._internalSourceColumnOrdinal == -1) - { - string unquotedColumnName = UnquotedName(bulkCopyColumn.SourceColumn); - - switch (_rowSourceType) - { - case ValueSourceType.DataTable: - index = ((DataTable)_rowSource).Columns.IndexOf(unquotedColumnName); - break; - case ValueSourceType.RowArray: - index = ((DataRow[])_rowSource)[0].Table.Columns.IndexOf(unquotedColumnName); - break; - case ValueSourceType.DbDataReader: - case ValueSourceType.IDataReader: - try - { - index = ((IDataRecord)_rowSource).GetOrdinal(unquotedColumnName); - } - catch (IndexOutOfRangeException e) - { - throw SQL.BulkLoadNonMatchingColumnName(unquotedColumnName, e); - } - break; - } - if (index == -1) - { - throw SQL.BulkLoadNonMatchingColumnName(unquotedColumnName); - } - bulkCopyColumn._internalSourceColumnOrdinal = index; - } - } - } - } - } - - internal void OnConnectionClosed() - { - TdsParserStateObject stateObj = _stateObj; - if (stateObj != null) - { - stateObj.OnConnectionClosed(); - } - } - - private bool FireRowsCopiedEvent(long rowsCopied) - { - // Release lock to prevent possible deadlocks - SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection(); - bool semaphoreLock = internalConnection._parserLock.CanBeReleasedFromAnyThread; - internalConnection._parserLock.Release(); - - SqlRowsCopiedEventArgs eventArgs = new SqlRowsCopiedEventArgs(rowsCopied); - try - { - _insideRowsCopiedEvent = true; - SqlRowsCopied?.Invoke(this, eventArgs); - } - finally - { - _insideRowsCopiedEvent = false; - internalConnection._parserLock.Wait(canReleaseFromAnyThread: semaphoreLock); - } - return eventArgs.Abort; - } - - // Reads a cell and then writes it. - // Read may block at this moment since there is no getValueAsync or DownStream async at this moment. - // When _isAsyncBulkCopy == true: Write will return Task (when async method runs asynchronously) or Null (when async call actually ran synchronously) for performance. - // When _isAsyncBulkCopy == false: Writes are purely sync. This method return null at the end. - private Task ReadWriteColumnValueAsync(int col) - { - bool isSqlType; - bool isDataFeed; - bool isNull; - object value = GetValueFromSourceRow(col, out isSqlType, out isDataFeed, out isNull); //this will return Task/null in future: as rTask - - _SqlMetaData metadata = _sortedColumnMappings[col]._metadata; - if (!isDataFeed) - { - value = ConvertValue(value, metadata, isNull, ref isSqlType, out isDataFeed); - - // If column encryption is requested via connection string option, perform encryption here - if (!isNull && // if value is not NULL - metadata.isEncrypted) - { // If we are transparently encrypting - Debug.Assert(_parser.ShouldEncryptValuesForBulkCopy()); - value = _parser.EncryptColumnValue(value, metadata, metadata.column, _stateObj, isDataFeed, isSqlType); - isSqlType = false; // Its not a sql type anymore - } - } - - //write part - Task writeTask = null; - if (metadata.type != SqlDbType.Variant) - { - //this is the most common path - writeTask = _parser.WriteBulkCopyValue(value, metadata, _stateObj, isSqlType, isDataFeed, isNull); //returns Task/Null - } - else - { - // Target type shouldn't be encrypted - Debug.Assert(!metadata.isEncrypted, "Can't encrypt SQL Variant type"); - SqlBuffer.StorageType variantInternalType = SqlBuffer.StorageType.Empty; - if ((_sqlDataReaderRowSource != null) && (_connection.Is2008OrNewer)) - { - variantInternalType = _sqlDataReaderRowSource.GetVariantInternalStorageType(_sortedColumnMappings[col]._sourceColumnOrdinal); - } - - if (variantInternalType == SqlBuffer.StorageType.DateTime2) - { - _parser.WriteSqlVariantDateTime2(((DateTime)value), _stateObj); - } - else if (variantInternalType == SqlBuffer.StorageType.Date) - { - _parser.WriteSqlVariantDate(((DateTime)value), _stateObj); - } - else - { - writeTask = _parser.WriteSqlVariantDataRowValue(value, _stateObj); //returns Task/Null - } - } - - return writeTask; - } - - private Task RegisterForConnectionCloseNotification(Task outterTask) - { - SqlConnection connection = _connection; - if (connection == null) - { - // No connection - throw ADP.ClosedConnectionError(); - } - - return connection.RegisterForConnectionCloseNotification(outterTask, this, SqlReferenceCollection.BulkCopyTag); - } - - // Runs a loop to copy all columns of a single row. - // Maintains a state by remembering #columns copied so far (int col). - // Returned Task could be null in two cases: (1) _isAsyncBulkCopy == false, (2) _isAsyncBulkCopy == true but all async writes finished synchronously. - private Task CopyColumnsAsync(int col, TaskCompletionSource source = null) - { - Task resultTask = null, task = null; - int i; - try - { - for (i = col; i < _sortedColumnMappings.Count; i++) - { - task = ReadWriteColumnValueAsync(i); //First reads and then writes one cell value. Task 'task' is completed when reading task and writing task both are complete. - if (task != null) - break; //task != null means we have a pending read/write Task. - } - if (task != null) - { - if (source == null) - { - source = new TaskCompletionSource(); - resultTask = source.Task; - } - CopyColumnsAsyncSetupContinuation(source, task, i); - return resultTask; //associated task will be completed when all columns (i.e. the entire row) is written - } - if (source != null) - { - source.SetResult(null); - } - } - catch (Exception ex) - { - if (source != null) - { - source.TrySetException(ex); - } - else - { - throw; - } - } - return resultTask; - } - - // This is in its own method to avoid always allocating the lambda in CopyColumnsAsync - private void CopyColumnsAsyncSetupContinuation(TaskCompletionSource source, Task task, int i) - { - AsyncHelper.ContinueTaskWithState(task, source, this, - onSuccess: (object state) => - { - SqlBulkCopy sqlBulkCopy = (SqlBulkCopy)state; - if (i + 1 < sqlBulkCopy._sortedColumnMappings.Count) - { - sqlBulkCopy.CopyColumnsAsync(i + 1, source); //continue from the next column - } - else - { - source.SetResult(null); - } - }, - connectionToDoom: _connection.GetOpenTdsConnection() - ); - } - - // The notification logic. - private void CheckAndRaiseNotification() - { - bool abortOperation = false; //returns if the operation needs to be aborted. - Exception exception = null; - - _rowsCopied++; - - // Fire event logic - if (_notifyAfter > 0) - { - if (_rowsUntilNotification > 0) - { - if (--_rowsUntilNotification == 0) - { - // Fire event during operation. This is the users chance to abort the operation. - try - { - // It's also the user's chance to cause an exception. - _stateObj.BcpLock = true; - abortOperation = FireRowsCopiedEvent(_rowsCopied); - SqlClientEventSource.Log.TryTraceEvent(""); - - // In case the target connection is closed accidentally. - if (ConnectionState.Open != _connection.State) - { - exception = ADP.OpenConnectionRequired(nameof(CheckAndRaiseNotification), _connection.State); - } - } - catch (Exception e) - { - if (!ADP.IsCatchableExceptionType(e)) - { - exception = e; - } - else - { - exception = OperationAbortedException.Aborted(e); - } - } - finally - { - _stateObj.BcpLock = false; - } - if (!abortOperation) - { - _rowsUntilNotification = _notifyAfter; - } - } - } - } - if (!abortOperation && _rowsUntilNotification > _notifyAfter) - { - _rowsUntilNotification = _notifyAfter; // Update on decrement of count - } - if (exception == null && abortOperation) - { - exception = OperationAbortedException.Aborted(null); - } - if (_connection.State != ConnectionState.Open) - { - throw ADP.OpenConnectionRequired(SQL.WriteToServer, _connection.State); - } - if (exception != null) - { - _parser._asyncWrite = false; - Task writeTask = _parser.WriteBulkCopyDone(_stateObj); //We should complete the current batch up to this row. - Debug.Assert(writeTask == null, "Task should not pend while doing sync bulk copy"); - RunParser(); - AbortTransaction(); - throw exception; //this will be caught and put inside the Task's exception. - } - } - - // Checks for cancellation. If cancel requested, cancels the task and returns the cancelled task - private Task CheckForCancellation(CancellationToken cts, TaskCompletionSource tcs) - { - if (cts.IsCancellationRequested) - { - if (tcs == null) - { - tcs = new TaskCompletionSource(); - } - tcs.SetCanceled(); - return tcs.Task; - } - else - { - return null; - } - } - - // Copies all the rows in a batch. - // Maintains state machine with state variable: rowSoFar. - // Returned Task could be null in two cases: (1) _isAsyncBulkCopy == false, or (2) _isAsyncBulkCopy == true but all async writes finished synchronously. - private Task CopyRowsAsync(int rowsSoFar, int totalRows, CancellationToken cts, TaskCompletionSource source = null) - { - Task resultTask = null; - Task task = null; - int i; - try - { - // totalRows is batchsize which is 0 by default. In that case, we keep copying till the end (until _hasMoreRowToCopy == false). - for (i = rowsSoFar; (totalRows <= 0 || i < totalRows) && _hasMoreRowToCopy == true; i++) - { - if (_isAsyncBulkCopy == true) - { - resultTask = CheckForCancellation(cts, source); - if (resultTask != null) - { - return resultTask; // Task got cancelled! - } - } - - _stateObj.WriteByte(TdsEnums.SQLROW); - - task = CopyColumnsAsync(0); // Copy 1 row - - if (task == null) - { // Task is done. - CheckAndRaiseNotification(); // Check notification logic after copying the row - - // Now we will read the next row. - Task readTask = ReadFromRowSourceAsync(cts); // Read the next row. Caution: more is only valid if the task returns null. Otherwise, we wait for Task.Result - if (readTask != null) - { - if (source == null) - { - source = new TaskCompletionSource(); - } - resultTask = source.Task; - - AsyncHelper.ContinueTaskWithState(readTask, source, this, - onSuccess: (object state) => ((SqlBulkCopy)state).CopyRowsAsync(i + 1, totalRows, cts, source), - connectionToDoom: _connection.GetOpenTdsConnection() - ); - return resultTask; // Associated task will be completed when all rows are copied to server/exception/cancelled. - } - } - else - { // task != null, so add continuation for it. - source = source ?? new TaskCompletionSource(); - resultTask = source.Task; - - AsyncHelper.ContinueTaskWithState(task, source, this, - onSuccess: (object state) => - { - SqlBulkCopy sqlBulkCopy = (SqlBulkCopy)state; - sqlBulkCopy.CheckAndRaiseNotification(); // Check for notification now as the current row copy is done at this moment. - - Task readTask = sqlBulkCopy.ReadFromRowSourceAsync(cts); - if (readTask == null) - { - sqlBulkCopy.CopyRowsAsync(i + 1, totalRows, cts, source); - } - else - { - AsyncHelper.ContinueTaskWithState(readTask, source, sqlBulkCopy, - onSuccess: (object state2) => ((SqlBulkCopy)state2).CopyRowsAsync(i + 1, totalRows, cts, source), - connectionToDoom: _connection.GetOpenTdsConnection() - ); - } - }, - connectionToDoom: _connection.GetOpenTdsConnection() - ); - return resultTask; - } - } - - if (source != null) - { - source.TrySetResult(null); // This is set only on the last call of async copy. But may not be set if everything runs synchronously. - } - } - catch (Exception ex) - { - if (source != null) - { - source.TrySetException(ex); - } - else - { - throw; - } - } - return resultTask; - } - - // Copies all the batches in a loop. One iteration for one batch. - // state variable is essentially not needed. (however, _hasMoreRowToCopy might be thought as a state variable) - // Returned Task could be null in two cases: (1) _isAsyncBulkCopy == false, or (2) _isAsyncBulkCopy == true but all async writes finished synchronously. - private Task CopyBatchesAsync(BulkCopySimpleResultSet internalResults, string updateBulkCommandText, CancellationToken cts, TaskCompletionSource source = null) - { - Debug.Assert(source == null || !source.Task.IsCompleted, "Called into CopyBatchesAsync with a completed task!"); - try - { - while (_hasMoreRowToCopy) - { - //pre->before every batch: Transaction, BulkCmd and metadata are done. - SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection(); - - if (IsCopyOption(SqlBulkCopyOptions.UseInternalTransaction)) - { //internal transaction is started prior to each batch if the Option is set. - internalConnection.ThreadHasParserLockForClose = true; // In case of error, tell the connection we already have the parser lock - try - { - _internalTransaction = _connection.BeginTransaction(); - } - finally - { - internalConnection.ThreadHasParserLockForClose = false; - } - } - - Task commandTask = SubmitUpdateBulkCommand(updateBulkCommandText); - - if (commandTask == null) - { - Task continuedTask = CopyBatchesAsyncContinued(internalResults, updateBulkCommandText, cts, source); - if (continuedTask != null) - { - // Continuation will take care of re-calling CopyBatchesAsync - return continuedTask; - } - } - else - { - Debug.Assert(_isAsyncBulkCopy, "Task should not pend while doing sync bulk copy"); - if (source == null) - { - source = new TaskCompletionSource(); - } - - AsyncHelper.ContinueTaskWithState(commandTask, source, this, - onSuccess: (object state) => - { - SqlBulkCopy sqlBulkCopy = (SqlBulkCopy)state; - Task continuedTask = sqlBulkCopy.CopyBatchesAsyncContinued(internalResults, updateBulkCommandText, cts, source); - if (continuedTask == null) - { - // Continuation finished sync, recall into CopyBatchesAsync to continue - sqlBulkCopy.CopyBatchesAsync(internalResults, updateBulkCommandText, cts, source); - } - }, - connectionToDoom: _connection.GetOpenTdsConnection() - ); - return source.Task; - } - } - } - catch (Exception ex) - { - if (source != null) - { - source.TrySetException(ex); - return source.Task; - } - else - { - throw; - } - } - - // If we are here, then we finished everything - if (source != null) - { - source.SetResult(null); - return source.Task; - } - else - { - return null; - } - } - - // Writes the MetaData and a single batch. - // If this returns true, then the caller is responsible for starting the next stage. - private Task CopyBatchesAsyncContinued(BulkCopySimpleResultSet internalResults, string updateBulkCommandText, CancellationToken cts, TaskCompletionSource source) - { - Debug.Assert(source == null || !source.Task.IsCompleted, "Called into CopyBatchesAsync with a completed task!"); - try - { - WriteMetaData(internalResults); - - // Load encryption keys now (if needed) - _parser.LoadColumnEncryptionKeys( - internalResults[MetaDataResultId].MetaData, - _connection); - - Task task = CopyRowsAsync(0, _savedBatchSize, cts); // This is copying 1 batch of rows and setting _hasMoreRowToCopy = true/false. - - // post->after every batch - if (task != null) - { - Debug.Assert(_isAsyncBulkCopy, "Task should not pend while doing sync bulk copy"); - if (source == null) - { // First time only - source = new TaskCompletionSource(); - } - AsyncHelper.ContinueTaskWithState(task, source, this, - onSuccess: (object state) => - { - SqlBulkCopy sqlBulkCopy = (SqlBulkCopy)state; - Task continuedTask = sqlBulkCopy.CopyBatchesAsyncContinuedOnSuccess(internalResults, updateBulkCommandText, cts, source); - if (continuedTask == null) - { - // Continuation finished sync, recall into CopyBatchesAsync to continue - sqlBulkCopy.CopyBatchesAsync(internalResults, updateBulkCommandText, cts, source); - } - }, - onFailure: static (Exception _, object state) => ((SqlBulkCopy)state).CopyBatchesAsyncContinuedOnError(cleanupParser: false), - onCancellation: (object state) => ((SqlBulkCopy)state).CopyBatchesAsyncContinuedOnError(cleanupParser: true) -, - connectionToDoom: _connection.GetOpenTdsConnection()); - - return source.Task; - } - else - { - return CopyBatchesAsyncContinuedOnSuccess(internalResults, updateBulkCommandText, cts, source); - } - } - catch (Exception ex) - { - if (source != null) - { - source.TrySetException(ex); - return source.Task; - } - else - { - throw; - } - } - } - - // Takes care of finishing a single batch (write done, run parser, commit transaction). - // If this returns true, then the caller is responsible for starting the next stage. - private Task CopyBatchesAsyncContinuedOnSuccess(BulkCopySimpleResultSet internalResults, string updateBulkCommandText, CancellationToken cts, TaskCompletionSource source) - { - Debug.Assert(source == null || !source.Task.IsCompleted, "Called into CopyBatchesAsync with a completed task!"); - try - { - Task writeTask = _parser.WriteBulkCopyDone(_stateObj); - - if (writeTask == null) - { - RunParser(); - CommitTransaction(); - - return null; - } - else - { - Debug.Assert(_isAsyncBulkCopy, "Task should not pend while doing sync bulk copy"); - if (source == null) - { - source = new TaskCompletionSource(); - } - - AsyncHelper.ContinueTaskWithState(writeTask, source, this, - onSuccess: (object state) => - { - SqlBulkCopy sqlBulkCopy = (SqlBulkCopy)state; - try - { - sqlBulkCopy.RunParser(); - sqlBulkCopy.CommitTransaction(); - } - catch (Exception) - { - sqlBulkCopy.CopyBatchesAsyncContinuedOnError(cleanupParser: false); - throw; - } - - // Always call back into CopyBatchesAsync - sqlBulkCopy.CopyBatchesAsync(internalResults, updateBulkCommandText, cts, source); - }, - onFailure: static (Exception _, object state) => ((SqlBulkCopy)state).CopyBatchesAsyncContinuedOnError(cleanupParser: false), - connectionToDoom: _connection.GetOpenTdsConnection() - ); - return source.Task; - } - } - catch (Exception ex) - { - if (source != null) - { - source.TrySetException(ex); - return source.Task; - } - else - { - throw; - } - } - } - - // Takes care of cleaning up the parser, stateObj and transaction when CopyBatchesAsync fails. - private void CopyBatchesAsyncContinuedOnError(bool cleanupParser) - { - SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection(); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#endif //DEBUG - if ((cleanupParser) && (_parser != null) && (_stateObj != null)) - { - _parser._asyncWrite = false; - Task task = _parser.WriteBulkCopyDone(_stateObj); - Debug.Assert(task == null, "Write should not pend when error occurs"); - RunParser(); - } - - if (_stateObj != null) - { - CleanUpStateObject(); - } -#if DEBUG - } - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (OutOfMemoryException) - { - internalConnection.DoomThisConnection(); - throw; - } - catch (StackOverflowException) - { - internalConnection.DoomThisConnection(); - throw; - } - catch (ThreadAbortException) - { - internalConnection.DoomThisConnection(); - throw; - } - - AbortTransaction(); - } - - // Cleans the stateobj. Used in a number of places, specially in exceptions. - private void CleanUpStateObject(bool isCancelRequested = true) - { - if (_stateObj != null) - { - _parser.Connection.ThreadHasParserLockForClose = true; - try - { - _stateObj.ResetBuffer(); - _stateObj.ResetPacketCounters(); - // If _parser is closed, sending attention will raise debug assertion, so we avoid it (but not calling CancelRequest). - if (isCancelRequested && (_parser.State == TdsParserState.OpenNotLoggedIn || _parser.State == TdsParserState.OpenLoggedIn)) - { - _stateObj.CancelRequest(); - } - _stateObj.SetTimeoutStateStopped(); - _stateObj.CloseSession(); - _stateObj._bulkCopyOpperationInProgress = false; - _stateObj._bulkCopyWriteTimeout = false; - _stateObj = null; - } - finally - { - _parser.Connection.ThreadHasParserLockForClose = false; - } - } - } - - // The continuation part of WriteToServerInternalRest. Executes when the initial query task is completed. (see, WriteToServerInternalRest). - // It carries on the source which is passed from the WriteToServerInternalRest and performs SetResult when the entire copy is done. - // The carried on source may be null in case of Sync copy. So no need to SetResult at that time. - // It launches the copy operation. - private void WriteToServerInternalRestContinuedAsync(BulkCopySimpleResultSet internalResults, CancellationToken cts, TaskCompletionSource source) - { - Task task = null; - string updateBulkCommandText = null; - - try - { - updateBulkCommandText = AnalyzeTargetAndCreateUpdateBulkCommand(internalResults); - - if (_sortedColumnMappings.Count != 0) - { - _stateObj.SniContext = SniContext.Snix_SendRows; - _savedBatchSize = _batchSize; // For safety. If someone changes the batchsize during copy we still be using _savedBatchSize. - _rowsUntilNotification = _notifyAfter; - _rowsCopied = 0; - - _currentRowMetadata = new SourceColumnMetadata[_sortedColumnMappings.Count]; - for (int i = 0; i < _currentRowMetadata.Length; i++) - { - _currentRowMetadata[i] = GetColumnMetadata(i); - } - - task = CopyBatchesAsync(internalResults, updateBulkCommandText, cts); // Launch the BulkCopy - } - - if (task != null) - { - if (source == null) - { - source = new TaskCompletionSource(); - } - AsyncHelper.ContinueTaskWithState(task, source, this, - onSuccess: (object state) => - { - SqlBulkCopy sqlBulkCopy = (SqlBulkCopy)state; - // Bulk copy task is completed at this moment. - if (task.IsCanceled) - { - sqlBulkCopy._localColumnMappings = null; - try - { - sqlBulkCopy.CleanUpStateObject(); - } - finally - { - source.SetCanceled(); - } - } - else if (task.Exception != null) - { - source.SetException(task.Exception.InnerException); - } - else - { - sqlBulkCopy._localColumnMappings = null; - try - { - sqlBulkCopy.CleanUpStateObject(isCancelRequested: false); - } - finally - { - if (source != null) - { - if (cts.IsCancellationRequested) - { // We may get cancellation req even after the entire copy. - source.SetCanceled(); - } - else - { - source.SetResult(null); - } - } - } - } - }, - connectionToDoom: _connection.GetOpenTdsConnection() - ); - return; - } - else - { - _localColumnMappings = null; - - try - { - CleanUpStateObject(isCancelRequested: false); - } - catch (Exception cleanupEx) - { - Debug.Fail($"Unexpected exception during {nameof(CleanUpStateObject)} (ignored)", cleanupEx.ToString()); - } - - if (source != null) - { - source.SetResult(null); - } - } - } - catch (Exception ex) - { - _localColumnMappings = null; - - try - { - CleanUpStateObject(); - } - catch (Exception cleanupEx) - { - Debug.Fail($"Unexpected exception during {nameof(CleanUpStateObject)} (ignored)", cleanupEx.ToString()); - } - - if (source != null) - { - source.TrySetException(ex); - } - else - { - throw; - } - } - } - - // Rest of the WriteToServerInternalAsync method. - // It carries on the source from its caller WriteToServerInternal. - // source is null in case of Sync bcp. But valid in case of Async bcp. - // It calls the WriteToServerInternalRestContinuedAsync as a continuation of the initial query task. - private void WriteToServerInternalRestAsync(CancellationToken cts, TaskCompletionSource source) - { - Debug.Assert(_hasMoreRowToCopy, "first time it is true, otherwise this method would not have been called."); - _hasMoreRowToCopy = true; - Task internalResultsTask = null; - BulkCopySimpleResultSet internalResults = new BulkCopySimpleResultSet(); - SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection(); - try - { - _parser = _connection.Parser; - _parser._asyncWrite = _isAsyncBulkCopy; // Very important! - - Task reconnectTask; - try - { - reconnectTask = _connection.ValidateAndReconnect( - () => - { - if (_parserLock != null) - { - _parserLock.Release(); - _parserLock = null; - } - }, BulkCopyTimeout); - } - catch (SqlException ex) - { - throw SQL.BulkLoadInvalidDestinationTable(_destinationTableName, ex); - } - - if (reconnectTask != null) - { - if (_isAsyncBulkCopy) - { - StrongBox regReconnectCancel = new StrongBox(new CancellationTokenRegistration()); - TaskCompletionSource cancellableReconnectTS = new TaskCompletionSource(); - if (cts.CanBeCanceled) - { - regReconnectCancel.Value = cts.Register(() => cancellableReconnectTS.TrySetCanceled()); - } - AsyncHelper.ContinueTaskWithState(reconnectTask, cancellableReconnectTS, cancellableReconnectTS, - onSuccess: static (object state) => ((TaskCompletionSource)state).SetResult(null) - ); - // No need to cancel timer since SqlBulkCopy creates specific task source for reconnection - AsyncHelper.SetTimeoutException(cancellableReconnectTS, BulkCopyTimeout, - () => { return SQL.BulkLoadInvalidDestinationTable(_destinationTableName, SQL.CR_ReconnectTimeout()); }, CancellationToken.None); - AsyncHelper.ContinueTaskWithState(cancellableReconnectTS.Task, source, regReconnectCancel, - onSuccess: (object state) => - { - ((StrongBox)state).Value.Dispose(); - if (_parserLock != null) - { - _parserLock.Release(); - _parserLock = null; - } - _parserLock = _connection.GetOpenTdsConnection()._parserLock; - _parserLock.Wait(canReleaseFromAnyThread: true); - WriteToServerInternalRestAsync(cts, source); - }, - connectionToAbort: _connection, - onFailure: static (Exception _, object state) => ((StrongBox)state).Value.Dispose(), - onCancellation: static (object state) => ((StrongBox)state).Value.Dispose(), - exceptionConverter: (Exception ex, object state) => SQL.BulkLoadInvalidDestinationTable(_destinationTableName, ex) - ); - return; - } - else - { - try - { - AsyncHelper.WaitForCompletion(reconnectTask, BulkCopyTimeout, static () => throw SQL.CR_ReconnectTimeout()); - } - catch (SqlException ex) - { - throw SQL.BulkLoadInvalidDestinationTable(_destinationTableName, ex); // Preserve behavior (throw InvalidOperationException on failure to connect) - } - _parserLock = _connection.GetOpenTdsConnection()._parserLock; - _parserLock.Wait(canReleaseFromAnyThread: false); - WriteToServerInternalRestAsync(cts, source); - return; - } - } - if (_isAsyncBulkCopy) - { - _connection.AddWeakReference(this, SqlReferenceCollection.BulkCopyTag); - } - - internalConnection.ThreadHasParserLockForClose = true; // In case of error, let the connection know that we already have the parser lock. - - try - { - _stateObj = _parser.GetSession(this); - _stateObj._bulkCopyOpperationInProgress = true; - _stateObj.StartSession(ObjectID); - } - finally - { - internalConnection.ThreadHasParserLockForClose = false; - } - - try - { - internalResultsTask = CreateAndExecuteInitialQueryAsync(out internalResults); // Task/Null - } - catch (SqlException ex) - { - throw SQL.BulkLoadInvalidDestinationTable(_destinationTableName, ex); - } - - if (internalResultsTask != null) - { - AsyncHelper.ContinueTaskWithState(internalResultsTask, source, this, - onSuccess: (object state) => ((SqlBulkCopy)state).WriteToServerInternalRestContinuedAsync(internalResultsTask.Result, cts, source), - connectionToDoom: _connection.GetOpenTdsConnection() - ); - } - else - { - Debug.Assert(internalResults != null, "Executing initial query finished synchronously, but there were no results"); - WriteToServerInternalRestContinuedAsync(internalResults, cts, source); // internalResults is valid here. - } - } - catch (Exception ex) - { - if (source != null) - { - source.TrySetException(ex); - } - else - { - throw; - } - } - } - - // This returns Task for Async, Null for Sync - private Task WriteToServerInternalAsync(CancellationToken ctoken) - { - TaskCompletionSource source = null; - Task resultTask = null; - - if (_isAsyncBulkCopy) - { - source = new TaskCompletionSource(); // Creating the completion source/Task that we pass to application - resultTask = RegisterForConnectionCloseNotification(source.Task); - } - - if (_destinationTableName == null) - { - if (source != null) - { - source.SetException(SQL.BulkLoadMissingDestinationTable()); // No table to copy - } - else - { - throw SQL.BulkLoadMissingDestinationTable(); - } - return resultTask; - } - - try - { - Task readTask = ReadFromRowSourceAsync(ctoken); // readTask == reading task. This is the first read call. "more" is valid only if readTask == null; - - if (readTask == null) - { // Synchronously finished reading. - if (!_hasMoreRowToCopy) - { // No rows in the source to copy! - if (source != null) - { - source.SetResult(null); - } - return resultTask; - } - else - { // True, we have more rows. - WriteToServerInternalRestAsync(ctoken, source); //rest of the method, passing the same completion and returning the incomplete task (ret). - return resultTask; - } - } - else - { - Debug.Assert(_isAsyncBulkCopy, "Read must not return a Task in the Sync mode"); - AsyncHelper.ContinueTaskWithState(readTask, source, this, - onSuccess: (object state) => - { - SqlBulkCopy sqlBulkCopy = (SqlBulkCopy)state; - if (!sqlBulkCopy._hasMoreRowToCopy) - { - source.SetResult(null); // No rows to copy! - } - else - { - sqlBulkCopy.WriteToServerInternalRestAsync(ctoken, source); // Passing the same completion which will be completed by the Callee. - } - }, - connectionToDoom: _connection.GetOpenTdsConnection() - ); - return resultTask; - } - } - catch (Exception ex) - { - if (source != null) - { - source.TrySetException(ex); - } - else - { - throw; - } - } - return resultTask; - } - - private void ResetWriteToServerGlobalVariables() - { - _dataTableSource = null; - _dbDataReaderRowSource = null; - _isAsyncBulkCopy = false; - _rowEnumerator = null; - _rowSource = null; - _rowSourceType = ValueSourceType.Unspecified; - _sqlDataReaderRowSource = null; - _sqlDataReaderRowSource = null; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlClientPermission.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlClientPermission.cs deleted file mode 100644 index 7672804655..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlClientPermission.cs +++ /dev/null @@ -1,432 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections; -using System.Data; -using System.Data.Common; -using System.Globalization; -using System.Reflection; -using System.Security; -using System.Security.Permissions; -using Microsoft.Data.Common; -using DBDataPermission = System.Data.Common.DBDataPermission; - -namespace Microsoft.Data.SqlClient -{ - /// - [Serializable] - public sealed class SqlClientPermission : System.Data.Common.DBDataPermission - { - - /// - [Obsolete("SqlClientPermission() has been deprecated. Use the SqlClientPermission(PermissionState.None) constructor. http://go.microsoft.com/fwlink/?linkid=14202", true)] // MDAC 86034 - public SqlClientPermission() : this(PermissionState.None) - { - } - - /// - public SqlClientPermission(PermissionState state) : base(state) - { - } - - /// - [Obsolete("SqlClientPermission(PermissionState state, Boolean allowBlankPassword) has been deprecated. Use the SqlClientPermission(PermissionState.None) constructor. http://go.microsoft.com/fwlink/?linkid=14202", true)] // MDAC 86034 - public SqlClientPermission(PermissionState state, bool allowBlankPassword) : this(state) - { - AllowBlankPassword = allowBlankPassword; - } - - private SqlClientPermission(SqlClientPermission permission) : base(permission) - { // for Copy - // Explicitly copyFrom to clone the key value pairs - CopyFrom(permission); - } - - internal SqlClientPermission(SqlClientPermissionAttribute permissionAttribute) : base(permissionAttribute) - { // for CreatePermission - } - - internal SqlClientPermission(SqlConnectionString constr) : base(PermissionState.None) - { // for Open - if (constr != null) - { - AllowBlankPassword = constr.HasBlankPassword; // MDAC 84563 - AddPermissionEntry(new DBConnectionString(constr)); - } - if (constr == null || constr.IsEmpty) - { - base.Add("", "", KeyRestrictionBehavior.AllowOnly); - } - } - - /// - public override void Add(string connectionString, string restrictions, KeyRestrictionBehavior behavior) - { - DBConnectionString constr = new DBConnectionString(connectionString, restrictions, behavior, SqlConnectionString.GetParseSynonyms(), false); - AddPermissionEntry(constr); - } - - /// - override public IPermission Copy() - { - return new SqlClientPermission(this); - } - - // Modifications - - private NameValuePermission _keyvaluetree = NameValuePermission.Default; - private /*DBConnectionString[]*/ArrayList _keyvalues; // = null; - - internal void AddPermissionEntry(DBConnectionString entry) - { - if (_keyvaluetree == null) - { - _keyvaluetree = new NameValuePermission(); - } - if (_keyvalues == null) - { - _keyvalues = new ArrayList(); - } - NameValuePermission.AddEntry(_keyvaluetree, _keyvalues, entry); - _IsUnrestricted = false; // MDAC 84639 - } - - private bool _IsUnrestricted - { - set - { - // Use Reflection to access the base class _isUnrestricted. There is no other way to alter this externally. - FieldInfo fieldInfo = GetType().BaseType.GetField("_isUnrestricted", BindingFlags.Instance - | BindingFlags.NonPublic); - fieldInfo.SetValue(this, value); - } - - get - { - return base.IsUnrestricted(); - } - } - - // Modified CopyFrom to make sure that we copy the Name Value Pair - private void CopyFrom(SqlClientPermission permission) - { - if (!_IsUnrestricted) - { - if (permission._keyvalues != null) - { - _keyvalues = (ArrayList)permission._keyvalues.Clone(); - - if (permission._keyvaluetree != null) - { - _keyvaluetree = permission._keyvaluetree.CopyNameValue(); - } - } - } - } - - /// - override public IPermission Intersect(IPermission target) - { // used during Deny actions - if (target == null) - { - return null; - } - if (target.GetType() != this.GetType()) - { - throw ADP.PermissionTypeMismatch(); - } - if (this.IsUnrestricted()) - { // MDAC 84803, NDPWhidbey 29121 - return target.Copy(); - } - - DBDataPermission operand = (DBDataPermission)target; - if (operand.IsUnrestricted()) - { // NDPWhidbey 29121 - return this.Copy(); - } - - SqlClientPermission newPermission = (SqlClientPermission)operand.Copy(); - newPermission.AllowBlankPassword &= AllowBlankPassword; - - if (_keyvalues != null && newPermission._keyvalues != null) - { - newPermission._keyvalues.Clear(); - - newPermission._keyvaluetree.Intersect(newPermission._keyvalues, _keyvaluetree); - } - else - { - // either target.Add or this.Add have not been called - // return a non-null object so IsSubset calls will fail - newPermission._keyvalues = null; - newPermission._keyvaluetree = null; - } - - if (newPermission.IsEmpty()) - { // no intersection, MDAC 86773 - newPermission = null; - } - return newPermission; - } - - private bool IsEmpty() - { // MDAC 84804 - ArrayList keyvalues = _keyvalues; - bool flag = !IsUnrestricted() && !AllowBlankPassword && (keyvalues == null || (0 == keyvalues.Count)); - return flag; - } - - /// - override public bool IsSubsetOf(IPermission target) - { - if (null == target) - { - return IsEmpty(); - } - if (target.GetType() != this.GetType()) - { - throw ADP.PermissionTypeMismatch(); - } - - SqlClientPermission superset = (target as SqlClientPermission); - - bool subset = superset.IsUnrestricted(); - if (!subset) - { - if (!IsUnrestricted() && - (!AllowBlankPassword || superset.AllowBlankPassword) && - (_keyvalues == null || superset._keyvaluetree != null)) - { - - subset = true; - if (_keyvalues != null) - { - foreach (DBConnectionString kventry in _keyvalues) - { - if (!superset._keyvaluetree.CheckValueForKeyPermit(kventry)) - { - subset = false; - break; - } - } - } - } - } - return subset; - } - - /// - override public IPermission Union(IPermission target) - { - if (target == null) - { - return this.Copy(); - } - if (target.GetType() != this.GetType()) - { - throw ADP.PermissionTypeMismatch(); - } - if (IsUnrestricted()) - { // MDAC 84803 - return this.Copy(); - } - - SqlClientPermission newPermission = (SqlClientPermission)target.Copy(); - if (!newPermission.IsUnrestricted()) - { - newPermission.AllowBlankPassword |= AllowBlankPassword; - - if (_keyvalues != null) - { - foreach (DBConnectionString entry in _keyvalues) - { - newPermission.AddPermissionEntry(entry); - } - } - } - return (newPermission.IsEmpty() ? null : newPermission); - } - - private string DecodeXmlValue(string value) - { - if (value != null && (0 < value.Length)) - { - value = value.Replace(""", "\""); - value = value.Replace("'", "\'"); - value = value.Replace("<", "<"); - value = value.Replace(">", ">"); - value = value.Replace("&", "&"); - } - return value; - } - - private string EncodeXmlValue(string value) - { - if (value != null && (0 < value.Length)) - { - value = value.Replace('\0', ' '); // assumption that '\0' will only be at end of string - value = value.Trim(); - value = value.Replace("&", "&"); - value = value.Replace(">", ">"); - value = value.Replace("<", "<"); - value = value.Replace("\'", "'"); - value = value.Replace("\"", """); - } - return value; - } - - // - // - // - /// - override public void FromXml(SecurityElement securityElement) - { - // code derived from CodeAccessPermission.ValidateElement - if (securityElement == null) - { - throw ADP.ArgumentNull("securityElement"); - } - string tag = securityElement.Tag; - if (!tag.Equals(XmlStr._Permission) && !tag.Equals(XmlStr._IPermission)) - { - throw ADP.NotAPermissionElement(); - } - String version = securityElement.Attribute(XmlStr._Version); - if (version != null && !version.Equals(XmlStr._VersionNumber)) - { - throw ADP.InvalidXMLBadVersion(); - } - - string unrestrictedValue = securityElement.Attribute(XmlStr._Unrestricted); - _IsUnrestricted = unrestrictedValue != null && Boolean.Parse(unrestrictedValue); - - Clear(); // MDAC 83105 - if (!_IsUnrestricted) - { - string allowNull = securityElement.Attribute(XmlStr._AllowBlankPassword); - AllowBlankPassword = allowNull != null && Boolean.Parse(allowNull); - - ArrayList children = securityElement.Children; - if (children != null) - { - foreach (SecurityElement keyElement in children) - { - tag = keyElement.Tag; - if (XmlStr._add == tag || (tag != null && XmlStr._add == tag.ToLower(CultureInfo.InvariantCulture))) - { - string constr = keyElement.Attribute(XmlStr._ConnectionString); - string restrt = keyElement.Attribute(XmlStr._KeyRestrictions); - string behavr = keyElement.Attribute(XmlStr._KeyRestrictionBehavior); - - KeyRestrictionBehavior behavior = KeyRestrictionBehavior.AllowOnly; - if (behavr != null) - { - behavior = (KeyRestrictionBehavior)Enum.Parse(typeof(KeyRestrictionBehavior), behavr, true); - } - constr = DecodeXmlValue(constr); - restrt = DecodeXmlValue(restrt); - Add(constr, restrt, behavior); - } - } - } - } - else - { - AllowBlankPassword = false; - } - } - - // - // - // - // - // - /// - override public SecurityElement ToXml() - { - Type type = this.GetType(); - SecurityElement root = new SecurityElement(XmlStr._IPermission); - root.AddAttribute(XmlStr._class, type.AssemblyQualifiedName.Replace('\"', '\'')); - root.AddAttribute(XmlStr._Version, XmlStr._VersionNumber); - - if (IsUnrestricted()) - { - root.AddAttribute(XmlStr._Unrestricted, XmlStr._true); - } - else - { - root.AddAttribute(XmlStr._AllowBlankPassword, AllowBlankPassword.ToString(CultureInfo.InvariantCulture)); - - if (_keyvalues != null) - { - foreach (DBConnectionString value in _keyvalues) - { - SecurityElement valueElement = new SecurityElement(XmlStr._add); - string tmp; - - tmp = value.ConnectionString; // WebData 97375 - tmp = EncodeXmlValue(tmp); - if (!ADP.IsEmpty(tmp)) - { - valueElement.AddAttribute(XmlStr._ConnectionString, tmp); - } - tmp = value.Restrictions; - tmp = EncodeXmlValue(tmp); - if (tmp == null) - { - tmp = ""; - } - valueElement.AddAttribute(XmlStr._KeyRestrictions, tmp); - - tmp = value.Behavior.ToString(); - valueElement.AddAttribute(XmlStr._KeyRestrictionBehavior, tmp); - - root.AddChild(valueElement); - } - } - } - return root; - } - - private static class XmlStr - { - internal const string _class = "class"; - internal const string _IPermission = "IPermission"; - internal const string _Permission = "Permission"; - internal const string _Unrestricted = "Unrestricted"; - internal const string _AllowBlankPassword = "AllowBlankPassword"; - internal const string _true = "true"; - internal const string _Version = "version"; - internal const string _VersionNumber = "1"; - - internal const string _add = "add"; - - internal const string _ConnectionString = "ConnectionString"; - internal const string _KeyRestrictions = "KeyRestrictions"; - internal const string _KeyRestrictionBehavior = "KeyRestrictionBehavior"; - } - - - } - - /// - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)] - [Serializable] - public sealed class SqlClientPermissionAttribute : DBDataPermissionAttribute - { - - /// - public SqlClientPermissionAttribute(SecurityAction action) : base(action) - { - } - - /// - override public IPermission CreatePermission() - { - return new SqlClientPermission(this); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlClientWrapperSmiStream.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlClientWrapperSmiStream.cs deleted file mode 100644 index a728b608ed..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlClientWrapperSmiStream.cs +++ /dev/null @@ -1,110 +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.Diagnostics; -using System.IO; - -namespace Microsoft.Data.SqlClient.Server -{ - // Simple wrapper over SmiStream that handles server events on the SqlClient side of Smi - internal class SqlClientWrapperSmiStream : Stream - { - - private SmiEventSink_Default _sink; - private SmiStream _stream; - - internal SqlClientWrapperSmiStream(SmiEventSink_Default sink, SmiStream stream) - { - Debug.Assert(sink != null); - Debug.Assert(stream != null); - _sink = sink; - _stream = stream; - } - - public override bool CanRead - { - get - { - return _stream.CanRead; - } - } - - // If CanSeek is false, Position, Seek, Length, and SetLength should throw. - public override bool CanSeek - { - get - { - return _stream.CanSeek; - } - } - - public override bool CanWrite - { - get - { - return _stream.CanWrite; - } - } - - public override long Length - { - get - { - long length = _stream.GetLength(_sink); - _sink.ProcessMessagesAndThrow(); - return length; - } - } - - public override long Position - { - get - { - long position = _stream.GetPosition(_sink); - _sink.ProcessMessagesAndThrow(); - return position; - } - set - { - _stream.SetPosition(_sink, value); - _sink.ProcessMessagesAndThrow(); - } - } - - public override void Flush() - { - _stream.Flush(_sink); - _sink.ProcessMessagesAndThrow(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - long result = _stream.Seek(_sink, offset, origin); - _sink.ProcessMessagesAndThrow(); - return result; - } - - public override void SetLength(long value) - { - _stream.SetLength(_sink, value); - _sink.ProcessMessagesAndThrow(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - int bytesRead = _stream.Read(_sink, buffer, offset, count); - _sink.ProcessMessagesAndThrow(); - return bytesRead; - } - - public override void Write(byte[] buffer, int offset, int count) - { - _stream.Write(_sink, buffer, offset, count); - _sink.ProcessMessagesAndThrow(); - } - } - -} - - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlClientWrapperSmiStreamChars.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlClientWrapperSmiStreamChars.cs deleted file mode 100644 index 99eeb4d91c..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlClientWrapperSmiStreamChars.cs +++ /dev/null @@ -1,147 +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.Diagnostics; -using System.IO; -using Microsoft.Data.Common; -using Microsoft.Data.SqlTypes; - -namespace Microsoft.Data.SqlClient.Server -{ - // Simple SqlStreamChars wrapper over SmiStream that handles server events on the - // SqlClient side of Smi - internal class SqlClientWrapperSmiStreamChars : SqlStreamChars - { - - private SmiEventSink_Default _sink; - private SmiStream _stream; - - internal SqlClientWrapperSmiStreamChars(SmiEventSink_Default sink, SmiStream stream) - { - Debug.Assert(sink != null); - Debug.Assert(stream != null); - _sink = sink; - _stream = stream; - } - - public override bool IsNull - { - get - { - return _stream == null; - } - } - - public override bool CanRead - { - get - { - return _stream.CanRead; - } - } - - // If CanSeek is false, Position, Seek, Length, and SetLength should throw. - public override bool CanSeek - { - get - { - return _stream.CanSeek; - } - } - - public override bool CanWrite - { - get - { - return _stream.CanWrite; - } - } - - public override long Length - { - get - { - long length = _stream.GetLength(_sink); - _sink.ProcessMessagesAndThrow(); - if (length > 0) - return length / sizeof(char); - else - return length; - } - } - - public override long Position - { - get - { - long position = _stream.GetPosition(_sink) / sizeof(char); - _sink.ProcessMessagesAndThrow(); - return position; - } - set - { - if (value < 0) - { - throw ADP.ArgumentOutOfRange("Position"); - } - _stream.SetPosition(_sink, value * sizeof(char)); - _sink.ProcessMessagesAndThrow(); - } - } - - public override void Flush() - { - _stream.Flush(_sink); - _sink.ProcessMessagesAndThrow(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - long result = _stream.Seek(_sink, offset * sizeof(char), origin); - _sink.ProcessMessagesAndThrow(); - return result; - } - - public override void SetLength(long value) - { - if (value < 0) - { - throw ADP.ArgumentOutOfRange("value"); - } - _stream.SetLength(_sink, value * sizeof(char)); - _sink.ProcessMessagesAndThrow(); - } - - public override int Read(char[] buffer, int offset, int count) - { - int bytesRead = _stream.Read(_sink, buffer, offset * sizeof(char), count); - _sink.ProcessMessagesAndThrow(); - return bytesRead; - } - - public override void Write(char[] buffer, int offset, int count) - { - _stream.Write(_sink, buffer, offset, count); - _sink.ProcessMessagesAndThrow(); - } - - // Convenience methods to allow simple pulling/pushing of raw bytes - internal int Read(byte[] buffer, int offset, int count) - { - int bytesRead = _stream.Read(_sink, buffer, offset, count); - _sink.ProcessMessagesAndThrow(); - return bytesRead; - } - - internal void Write(byte[] buffer, int offset, int count) - { - _stream.Write(_sink, buffer, offset, count); - _sink.ProcessMessagesAndThrow(); - } - } - -} - - - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs index f66d9a811d..97212843f5 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlCommand.cs @@ -42,6 +42,82 @@ public sealed class SqlCommand : DbCommand, ICloneable private const int MaxRPCNameLength = 1046; internal readonly int ObjectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); + internal sealed class ExecuteReaderAsyncCallContext : AAsyncCallContext + { + public Guid OperationID; + public CommandBehavior CommandBehavior; + + public SqlCommand Command => _owner; + public TaskCompletionSource TaskCompletionSource => _source; + + public void Set(SqlCommand command, TaskCompletionSource source, CancellationTokenRegistration disposable, CommandBehavior behavior, Guid operationID) + { + base.Set(command, source, disposable); + CommandBehavior = behavior; + OperationID = operationID; + } + + protected override void Clear() + { + OperationID = default; + CommandBehavior = default; + } + + protected override void AfterCleared(SqlCommand owner) + { + owner?.SetCachedCommandExecuteReaderAsyncContext(this); + } + } + + internal sealed class ExecuteNonQueryAsyncCallContext : AAsyncCallContext + { + public Guid OperationID; + + public SqlCommand Command => _owner; + + public TaskCompletionSource TaskCompletionSource => _source; + + public void Set(SqlCommand command, TaskCompletionSource source, CancellationTokenRegistration disposable, Guid operationID) + { + base.Set(command, source, disposable); + OperationID = operationID; + } + + protected override void Clear() + { + OperationID = default; + } + + protected override void AfterCleared(SqlCommand owner) + { + owner?.SetCachedCommandExecuteNonQueryAsyncContext(this); + } + } + + internal sealed class ExecuteXmlReaderAsyncCallContext : AAsyncCallContext + { + public Guid OperationID; + + public SqlCommand Command => _owner; + public TaskCompletionSource TaskCompletionSource => _source; + + public void Set(SqlCommand command, TaskCompletionSource source, CancellationTokenRegistration disposable, Guid operationID) + { + base.Set(command, source, disposable); + OperationID = operationID; + } + + protected override void Clear() + { + OperationID = default; + } + + protected override void AfterCleared(SqlCommand owner) + { + owner?.SetCachedCommandExecuteXmlReaderContext(this); + } + } + private string _commandText; private CommandType _commandType; private int? _commandTimeout; @@ -344,101 +420,6 @@ private CachedAsyncState cachedAsyncState /// internal bool CachingQueryMetadataPostponed { get; set; } - // - // Smi execution-specific stuff - // - sealed private class CommandEventSink : SmiEventSink_Default - { - private SqlCommand _command; - - internal CommandEventSink(SqlCommand command) : base() - { - _command = command; - } - - internal override void StatementCompleted(int rowsAffected) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, rowsAffected={1}.", _command.ObjectID, rowsAffected); - _command.InternalRecordsAffected = rowsAffected; - - // UNDONE: need to fire events back to user code, but this may be called - // while in a callback from the native server. Calling user code in - // this situation is BAAAAD, because the user code could call back to - // the server. - // _command.OnStatementCompleted( rowsAffected ); - } - - internal override void BatchCompleted() - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}.", _command.ObjectID); - } - - internal override void ParametersAvailable(SmiParameterMetaData[] metaData, ITypedGettersV3 parameterValues) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} metaData.Length={1}.", _command.ObjectID, metaData?.Length); - - if (SqlClientEventSource.Log.IsAdvancedTraceOn()) - { - if (metaData != null) - { - for (int i = 0; i < metaData.Length; i++) - { - SqlClientEventSource.Log.AdvancedTraceEvent(" {0}, metaData[{1}] is {2}{3}", _command.ObjectID, i, metaData[i].GetType(), metaData[i].TraceString()); - } - } - } - - Debug.Assert(SmiContextFactory.Instance.NegotiatedSmiVersion >= SmiContextFactory.Sql2005Version); - _command.OnParametersAvailableSmi(metaData, parameterValues); - } - - internal override void ParameterAvailable(SmiParameterMetaData metaData, SmiTypedGetterSetter parameterValues, int ordinal) - { - if (SqlClientEventSource.Log.IsAdvancedTraceOn()) - { - SqlClientEventSource.Log.AdvancedTraceEvent(" {0}, metaData[{1}] is {2}{ 3}", _command.ObjectID, ordinal, metaData?.GetType(), metaData?.TraceString()); - } - Debug.Assert(SmiContextFactory.Instance.NegotiatedSmiVersion >= SmiContextFactory.Sql2008Version); - _command.OnParameterAvailableSmi(metaData, parameterValues, ordinal); - } - } - - private SmiContext _smiRequestContext; // context that _smiRequest came from - private CommandEventSink _smiEventSink; - private SmiEventSink_DeferedProcessing _outParamEventSink; - - private CommandEventSink EventSink - { - get - { - if (_smiEventSink == null) - { - _smiEventSink = new CommandEventSink(this); - } - - _smiEventSink.Parent = InternalSmiConnection.CurrentEventSink; - return _smiEventSink; - } - } - - private SmiEventSink_DeferedProcessing OutParamEventSink - { - get - { - if (_outParamEventSink == null) - { - _outParamEventSink = new SmiEventSink_DeferedProcessing(EventSink); - } - else - { - _outParamEventSink.Parent = EventSink; - } - - return _outParamEventSink; - } - } - - /// public SqlCommand() : base() { @@ -523,9 +504,6 @@ private SqlCommand(SqlCommand from) : this() _transaction = null; } - // If the connection has changes, then the request context may have changed as well - _smiRequestContext = null; - // Command is no longer prepared on new connection, cleanup prepare status if (IsPrepared) { @@ -534,22 +512,8 @@ private SqlCommand(SqlCommand from) : this() RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#endif //DEBUG - // cleanup - Unprepare(); -#if DEBUG - } - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG + // cleanup + Unprepare(); } catch (System.OutOfMemoryException) { @@ -596,14 +560,6 @@ protected override DbConnection DbConnection } } - private SqlInternalConnectionSmi InternalSmiConnection - { - get - { - return (SqlInternalConnectionSmi)_activeConnection.InnerConnection; - } - } - private SqlInternalConnectionTds InternalTdsConnection { get @@ -612,17 +568,6 @@ private SqlInternalConnectionTds InternalTdsConnection } } - private bool Is2000 - { - get - { - Debug.Assert(_activeConnection != null, "The active connection is null!"); - if (_activeConnection == null) - return false; - return _activeConnection.Is2000; - } - } - private bool IsProviderRetriable => SqlConfigurableRetryFactory.IsRetriable(RetryLogicProvider); /// @@ -1021,29 +966,18 @@ public override void Prepare() // between entry into Execute* API and the thread obtaining the stateObject. _pendingCancel = false; - // Context connection's prepare is a no-op - if (_activeConnection != null && _activeConnection.IsContextConnection) - { - return; - } - - SqlStatistics statistics = null; using (TryEventScope.Create(" {0}", ObjectID)) { SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current); - statistics = SqlStatistics.StartTimer(Statistics); + SqlStatistics statistics = SqlStatistics.StartTimer(Statistics); // only prepare if batch with parameters // MDAC BUG #'s 73776 & 72101 if ( - this.IsPrepared && !this.IsDirty - || (this.CommandType == CommandType.StoredProcedure) - || ( - (System.Data.CommandType.Text == this.CommandType) - && (0 == GetParameterCount(_parameters)) - ) - + IsPrepared && !IsDirty + || CommandType == CommandType.StoredProcedure + || (CommandType == CommandType.Text && GetParameterCount(_parameters) == 0) ) { if (Statistics != null) @@ -1077,23 +1011,7 @@ public override void Prepare() } } -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - InternalPrepare(); - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG + InternalPrepare(); } catch (System.OutOfMemoryException e) { @@ -1168,12 +1086,6 @@ private void InternalPrepare() // SqlInternalConnectionTds needs to be able to unprepare a statement internal void Unprepare() { - // Context connection's prepare is a no-op - if (_activeConnection.IsContextConnection) - { - return; - } - Debug.Assert(true == IsPrepared, "Invalid attempt to Unprepare a non-prepared command!"); Debug.Assert(_activeConnection != null, "must have an open connection to UnPrepare"); Debug.Assert(false == _inPrepare, "_inPrepare should be false!"); @@ -1261,48 +1173,31 @@ public override void Cancel() RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); - - if (!_pendingCancel) - { // Do nothing if aleady pending. - // Before attempting actual cancel, set the _pendingCancel flag to false. - // This denotes to other thread before obtaining stateObject from the - // session pool that there is another thread wishing to cancel. - // The period in question is between entering the ExecuteAPI and obtaining - // a stateObject. - _pendingCancel = true; - - TdsParserStateObject stateObj = _stateObj; - if (stateObj != null) - { - stateObj.Cancel(ObjectID); - } - else + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); + + if (!_pendingCancel) + { // Do nothing if aleady pending. + // Before attempting actual cancel, set the _pendingCancel flag to false. + // This denotes to other thread before obtaining stateObject from the + // session pool that there is another thread wishing to cancel. + // The period in question is between entering the ExecuteAPI and obtaining + // a stateObject. + _pendingCancel = true; + + TdsParserStateObject stateObj = _stateObj; + if (stateObj != null) + { + stateObj.Cancel(this); + } + else + { + SqlDataReader reader = connection.FindLiveReader(this); + if (reader != null) { - SqlDataReader reader = connection.FindLiveReader(this); - if (reader != null) - { - reader.Cancel(ObjectID); - } + reader.Cancel(this); } } } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG } catch (System.OutOfMemoryException e) { @@ -1559,32 +1454,6 @@ public override int ExecuteNonQuery() } } - // Handles in-proc execute-to-pipe functionality - // Identical to ExecuteNonQuery - internal void ExecuteToPipe(SmiContext pipeContext) - { - SqlConnection.ExecutePermission.Demand(); - - // Reset _pendingCancel upon entry into any Execute - used to synchronize state - // between entry into Execute* API and the thread obtaining the stateObject. - _pendingCancel = false; - - SqlStatistics statistics = null; - - using (TryEventScope.Create(" {0}", ObjectID)) - { - try - { - statistics = SqlStatistics.StartTimer(Statistics); - InternalExecuteNonQuery(null, nameof(ExecuteNonQuery), true, CommandTimeout, out _); - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - } - /// [System.Security.Permissions.HostProtectionAttribute(ExternalThreading = true)] public IAsyncResult BeginExecuteNonQuery() @@ -1685,27 +1554,10 @@ private void BeginExecuteNonQueryInternalReadStage(TaskCompletionSource RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); - // must finish caching information before ReadSni which can activate the callback before returning - cachedAsyncState.SetActiveConnectionAndResult(completion, nameof(EndExecuteNonQuery), _activeConnection); - _stateObj.ReadSni(completion); - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); + // must finish caching information before ReadSni which can activate the callback before returning + cachedAsyncState.SetActiveConnectionAndResult(completion, nameof(EndExecuteNonQuery), _activeConnection); + _stateObj.ReadSni(completion); } catch (System.OutOfMemoryException e) { @@ -1918,101 +1770,84 @@ private object InternalEndExecuteNonQuery(IAsyncResult asyncResult, string endMe try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); + VerifyEndExecuteState((Task)asyncResult, endMethod); + WaitForAsyncResults(asyncResult, isInternal); - RuntimeHelpers.PrepareConstrainedRegions(); - try + // If column encryption is enabled, also check the state after waiting for the task. + // It would be better to do this for all cases, but avoiding for compatibility reasons. + if (IsColumnEncryptionEnabled) { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); - VerifyEndExecuteState((Task)asyncResult, endMethod); - WaitForAsyncResults(asyncResult, isInternal); + VerifyEndExecuteState((Task)asyncResult, endMethod, fullCheckForColumnEncryption: true); + } - // If column encryption is enabled, also check the state after waiting for the task. - // It would be better to do this for all cases, but avoiding for compatibility reasons. - if (IsColumnEncryptionEnabled) + bool processFinallyBlock = true; + try + { + // If this is not for internal usage, notify the dependency. + // If we have already initiated the end internally, the reader should be ready, so just return the rows affected. + if (!isInternal) { - VerifyEndExecuteState((Task)asyncResult, endMethod, fullCheckForColumnEncryption: true); - } + NotifyDependency(); - bool processFinallyBlock = true; - try - { - // If this is not for internal usage, notify the dependency. - // If we have already initiated the end internally, the reader should be ready, so just return the rows affected. - if (!isInternal) + if (_internalEndExecuteInitiated) { - NotifyDependency(); - - if (_internalEndExecuteInitiated) - { - Debug.Assert(_stateObj == null); + Debug.Assert(_stateObj == null); - // Reset the state since we exit early. - cachedAsyncState.ResetAsyncState(); + // Reset the state since we exit early. + cachedAsyncState.ResetAsyncState(); - return _rowsAffected; - } + return _rowsAffected; } + } - CheckThrowSNIException(); + CheckThrowSNIException(); - // only send over SQL Batch command if we are not a stored proc and have no parameters - if ((System.Data.CommandType.Text == this.CommandType) && (0 == GetParameterCount(_parameters))) + // only send over SQL Batch command if we are not a stored proc and have no parameters + if ((System.Data.CommandType.Text == this.CommandType) && (0 == GetParameterCount(_parameters))) + { + try { - try - { - Debug.Assert(_stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - TdsOperationStatus result = _stateObj.Parser.TryRun(RunBehavior.UntilDone, this, null, null, _stateObj, out _); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - } - finally + Debug.Assert(_stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); + TdsOperationStatus result = _stateObj.Parser.TryRun(RunBehavior.UntilDone, this, null, null, _stateObj, out _); + if (result != TdsOperationStatus.Done) { - // Don't reset the state for internal End. The user End will do that eventually. - if (!isInternal) - { - cachedAsyncState.ResetAsyncState(); - } + throw SQL.SynchronousCallMayNotPend(); } } - else - { // otherwise, use a full-fledged execute that can handle params and stored procs - SqlDataReader reader = CompleteAsyncExecuteReader(isInternal); - if (reader != null) + finally + { + // Don't reset the state for internal End. The user End will do that eventually. + if (!isInternal) { - reader.Close(); + cachedAsyncState.ResetAsyncState(); } } } - catch (Exception e) - { - processFinallyBlock = ADP.IsCatchableExceptionType(e); - throw; - } - finally - { - if (processFinallyBlock) + else + { // otherwise, use a full-fledged execute that can handle params and stored procs + SqlDataReader reader = CompleteAsyncExecuteReader(isInternal); + if (reader != null) { - PutStateObject(); + reader.Close(); } } - - Debug.Assert(_stateObj == null, "non-null state object in EndExecuteNonQuery"); - return _rowsAffected; } -#if DEBUG + catch (Exception e) + { + processFinallyBlock = ADP.IsCatchableExceptionType(e); + throw; + } finally { - tdsReliabilitySection.Stop(); + if (processFinallyBlock) + { + PutStateObject(); + } } -#endif //DEBUG + + Debug.Assert(_stateObj == null, "non-null state object in EndExecuteNonQuery"); + return _rowsAffected; } catch (System.OutOfMemoryException e) { @@ -2046,88 +1881,60 @@ private Task InternalExecuteNonQuery(TaskCompletionSource completion, st RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); + // @devnote: this function may throw for an invalid connection + // @devnote: returns false for empty command text + if (!inRetry) { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); - // @devnote: this function may throw for an invalid connection - // @devnote: returns false for empty command text - if (!inRetry) - { - ValidateCommand(methodName, async); - } - CheckNotificationStateAndAutoEnlist(); // Only call after validate - requires non null connection! + ValidateCommand(methodName, async); + } + CheckNotificationStateAndAutoEnlist(); // Only call after validate - requires non null connection! - Task task = null; + Task task = null; - // only send over SQL Batch command if we are not a stored proc and have no parameters and not in batch RPC mode - if (_activeConnection.IsContextConnection) + //Always Encrypted generally operates only on parameterized queries. However enclave based Always encrypted also supports unparameterized queries + //We skip this block for enclave based always encrypted so that we can make a call to SQL Server to get the encryption information + if (!ShouldUseEnclaveBasedWorkflow && !_batchRPCMode && CommandType == CommandType.Text && GetParameterCount(_parameters) == 0) + { + Debug.Assert(!sendToPipe, "trying to send non-context command to pipe"); + if (statistics != null) { - if (statistics != null) + if (!IsDirty && IsPrepared) + { + statistics.SafeIncrement(ref statistics._preparedExecs); + } + else { statistics.SafeIncrement(ref statistics._unpreparedExecs); } - - RunExecuteNonQuerySmi(sendToPipe); } - //Always Encrypted generally operates only on parameterized queries. However enclave based Always encrypted also supports unparameterized queries - //We skip this block for enclave based always encrypted so that we can make a call to SQL Server to get the encryption information - else if (!ShouldUseEnclaveBasedWorkflow && !_batchRPCMode && (System.Data.CommandType.Text == this.CommandType) && (0 == GetParameterCount(_parameters))) - { - Debug.Assert(!sendToPipe, "trying to send non-context command to pipe"); - if (statistics != null) - { - if (!this.IsDirty && this.IsPrepared) - { - statistics.SafeIncrement(ref statistics._preparedExecs); - } - else - { - statistics.SafeIncrement(ref statistics._unpreparedExecs); - } - } + // We should never get here for a retry since we only have retries for parameters. + Debug.Assert(!inRetry); - // We should never get here for a retry since we only have retries for parameters. - Debug.Assert(!inRetry); + task = RunExecuteNonQueryTds(methodName, async, timeout, asyncWrite); + } + else + { + // otherwise, use a full-fledged execute that can handle params and stored procs + Debug.Assert(!sendToPipe, "trying to send non-context command to pipe"); + SqlClientEventSource.Log.TryTraceEvent(" {0}, Command executed as RPC.", ObjectID); - task = RunExecuteNonQueryTds(methodName, async, timeout, asyncWrite); - } - else + SqlDataReader reader = RunExecuteReader(0, RunBehavior.UntilDone, false, methodName, completion, timeout, out task, out usedCache, asyncWrite, inRetry); + if (reader != null) { - // otherwise, use a full-fledged execute that can handle params and stored procs - Debug.Assert(!sendToPipe, "trying to send non-context command to pipe"); - SqlClientEventSource.Log.TryTraceEvent(" {0}, Command executed as RPC.", ObjectID); - - SqlDataReader reader = RunExecuteReader(0, RunBehavior.UntilDone, false, methodName, completion, timeout, out task, out usedCache, asyncWrite, inRetry); - if (reader != null) + if (task != null) { - if (task != null) - { - task = AsyncHelper.CreateContinuationTask(task, () => reader.Close()); - } - else - { - reader.Close(); - } + task = AsyncHelper.CreateContinuationTask(task, () => reader.Close()); + } + else + { + reader.Close(); } } - Debug.Assert(async || _stateObj == null, "non-null state object in InternalExecuteNonQuery"); - return task; - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); } -#endif //DEBUG + Debug.Assert(async || _stateObj == null, "non-null state object in InternalExecuteNonQuery"); + return task; } catch (System.OutOfMemoryException e) { @@ -2293,27 +2100,10 @@ private void BeginExecuteXmlReaderInternalReadStage(TaskCompletionSource RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); - // must finish caching information before ReadSni which can activate the callback before returning - cachedAsyncState.SetActiveConnectionAndResult(completion, nameof(EndExecuteXmlReader), _activeConnection); - _stateObj.ReadSni(completion); - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); + // must finish caching information before ReadSni which can activate the callback before returning + cachedAsyncState.SetActiveConnectionAndResult(completion, nameof(EndExecuteXmlReader), _activeConnection); + _stateObj.ReadSni(completion); } catch (System.OutOfMemoryException e) { @@ -2555,28 +2345,11 @@ internal SqlDataReader ExecuteReader(CommandBehavior behavior, string method) try { WriteBeginExecuteEvent(); -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); - statistics = SqlStatistics.StartTimer(Statistics); - SqlDataReader result = RunExecuteReader(behavior, RunBehavior.ReturnImmediately, true, method); - success = true; - return result; - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); + statistics = SqlStatistics.StartTimer(Statistics); + SqlDataReader result = RunExecuteReader(behavior, RunBehavior.ReturnImmediately, true, method); + success = true; + return result; } catch (SqlException e) { @@ -2697,6 +2470,26 @@ private SqlDataReader EndExecuteReaderInternal(IAsyncResult asyncResult) } } + private void CleanupExecuteReaderAsync(Task task, TaskCompletionSource source, Guid operationId) + { + if (task.IsFaulted) + { + Exception e = task.Exception.InnerException; + source.SetException(e); + } + else + { + if (task.IsCanceled) + { + source.SetCanceled(); + } + else + { + source.SetResult(task.Result); + } + } + } + private IAsyncResult BeginExecuteReaderAsync(CommandBehavior behavior, AsyncCallback callback, object stateObject) { return BeginExecuteReaderInternal(behavior, callback, stateObject, CommandTimeout, inRetry: false, asyncWrite: true); @@ -2850,9 +2643,16 @@ private bool TriggerInternalEndAndRetryIfNecessary(CommandBehavior behavior, obj { _cachedAsyncState.ResetAsyncState(); } - _activeConnection.GetOpenTdsConnection().DecrementAsyncCount(); + try + { + _activeConnection.GetOpenTdsConnection().DecrementAsyncCount(); - globalCompletion.TrySetException(e); + globalCompletion.TrySetException(e); + } + catch (Exception e2) + { + globalCompletion.TrySetException(e2); + } } else { @@ -2867,21 +2667,26 @@ private bool TriggerInternalEndAndRetryIfNecessary(CommandBehavior behavior, obj _internalEndExecuteInitiated = false; Task retryTask = (Task)retryFunc(behavior, null, stateObject, TdsParserStaticMethods.GetRemainingTimeout(timeout, firstAttemptStart), true/*inRetry*/, asyncWrite); - retryTask.ContinueWith(retryTsk => - { - if (retryTsk.IsFaulted) - { - globalCompletion.TrySetException(retryTsk.Exception.InnerException); - } - else if (retryTsk.IsCanceled) + retryTask.ContinueWith( + static (Task retryTask, object state) => { - globalCompletion.TrySetCanceled(); - } - else - { - globalCompletion.TrySetResult(retryTsk.Result); - } - }, TaskScheduler.Default); + TaskCompletionSource completion = (TaskCompletionSource)state; + if (retryTask.IsFaulted) + { + completion.TrySetException(retryTask.Exception.InnerException); + } + else if (retryTask.IsCanceled) + { + completion.TrySetCanceled(); + } + else + { + completion.TrySetResult(retryTask.Result); + } + }, + state: globalCompletion, + TaskScheduler.Default + ); } catch (Exception e2) { @@ -2929,27 +2734,10 @@ private void BeginExecuteReaderInternalReadStage(TaskCompletionSource co RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); - // must finish caching information before ReadSni which can activate the callback before returning - cachedAsyncState.SetActiveConnectionAndResult(completion, nameof(EndExecuteReader), _activeConnection); - _stateObj.ReadSni(completion); - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); + // must finish caching information before ReadSni which can activate the callback before returning + cachedAsyncState.SetActiveConnectionAndResult(completion, nameof(EndExecuteReader), _activeConnection); + _stateObj.ReadSni(completion); } catch (System.OutOfMemoryException e) { @@ -3004,27 +2792,10 @@ private SqlDataReader InternalEndExecuteReader(IAsyncResult asyncResult, string RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); - SqlDataReader reader = CompleteAsyncExecuteReader(isInternal); - Debug.Assert(_stateObj == null, "non-null state object in InternalEndExecuteReader"); - return reader; - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); + SqlDataReader reader = CompleteAsyncExecuteReader(isInternal); + Debug.Assert(_stateObj == null, "non-null state object in InternalEndExecuteReader"); + return reader; } catch (System.OutOfMemoryException e) { @@ -3057,6 +2828,7 @@ private Task InternalExecuteNonQueryAsync(CancellationToken cancellationTok { SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current); SqlConnection.ExecutePermission.Demand(); + Guid operationId = Guid.Empty; // connection can be used as state in RegisterForConnectionCloseNotification continuation // to avoid an allocation so use it as the state value if possible but it can be changed if @@ -3075,39 +2847,67 @@ private Task InternalExecuteNonQueryAsync(CancellationToken cancellationTok } Task returnedTask = source.Task; + + ExecuteNonQueryAsyncCallContext context = new ExecuteNonQueryAsyncCallContext(); + context.Set(this, source, registration, operationId); try { returnedTask = RegisterForConnectionCloseNotification(returnedTask); - Task.Factory.FromAsync(BeginExecuteNonQueryAsync, EndExecuteNonQueryAsync, null).ContinueWith((t) => - { - registration.Dispose(); - if (t.IsFaulted) + Task.Factory.FromAsync( + beginMethod: static (AsyncCallback callback, object stateObject) => { - Exception e = t.Exception.InnerException; - source.SetException(e); - } - else + return ((ExecuteNonQueryAsyncCallContext)stateObject).Command.BeginExecuteNonQueryAsync(callback, stateObject); + }, + endMethod: static (IAsyncResult asyncResult) => { - if (t.IsCanceled) - { - source.SetCanceled(); - } - else - { - source.SetResult(t.Result); - } - } - }, TaskScheduler.Default); + return ((ExecuteNonQueryAsyncCallContext)asyncResult.AsyncState).Command.EndExecuteNonQueryAsync(asyncResult); + }, + state: context + ) + .ContinueWith( + static (Task task) => + { + ExecuteNonQueryAsyncCallContext context = (ExecuteNonQueryAsyncCallContext)task.AsyncState; + SqlCommand command = context.Command; + Guid operationId = context.OperationID; + TaskCompletionSource source = context.TaskCompletionSource; + context.Dispose(); + + command.CleanupAfterExecuteNonQueryAsync(task, source, operationId); + }, + scheduler: TaskScheduler.Default + ); } catch (Exception e) { source.SetException(e); + context.Dispose(); } return returnedTask; } + private void CleanupAfterExecuteNonQueryAsync(Task task, TaskCompletionSource source, Guid operationId) + { + if (task.IsFaulted) + { + Exception e = task.Exception.InnerException; + source.SetException(e); + } + else + { + if (task.IsCanceled) + { + source.SetCanceled(); + } + else + { + source.SetResult(task.Result); + } + } + } + /// protected override Task ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) { @@ -3157,6 +2957,7 @@ private Task InternalExecuteReaderAsync(CommandBehavior behavior, { SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID {0}, behavior={1}, ActivityID {2}", ObjectID, (int)behavior, ActivityCorrelator.Current); SqlConnection.ExecutePermission.Demand(); + Guid operationId = default(Guid); // connection can be used as state in RegisterForConnectionCloseNotification continuation // to avoid an allocation so use it as the state value if possible but it can be changed if @@ -3175,39 +2976,80 @@ private Task InternalExecuteReaderAsync(CommandBehavior behavior, } Task returnedTask = source.Task; + ExecuteReaderAsyncCallContext context = null; try { returnedTask = RegisterForConnectionCloseNotification(returnedTask); - Task.Factory.FromAsync(BeginExecuteReaderAsync, EndExecuteReaderAsync, behavior, null).ContinueWith((t) => + if (_activeConnection?.InnerConnection is SqlInternalConnection sqlInternalConnection) + { + context = Interlocked.Exchange(ref sqlInternalConnection.CachedCommandExecuteReaderAsyncContext, null); + } + if (context is null) { - registration.Dispose(); - if (t.IsFaulted) + context = new ExecuteReaderAsyncCallContext(); + } + context.Set(this, source, registration, behavior, operationId); + + Task.Factory.FromAsync( + beginMethod: static (AsyncCallback callback, object stateObject) => { - Exception e = t.Exception.InnerException; - source.SetException(e); - } - else + ExecuteReaderAsyncCallContext args = (ExecuteReaderAsyncCallContext)stateObject; + return args.Command.BeginExecuteReaderInternal(args.CommandBehavior, callback, stateObject, args.Command.CommandTimeout, inRetry: false, asyncWrite: true); + }, + endMethod: static (IAsyncResult asyncResult) => { - if (t.IsCanceled) - { - source.SetCanceled(); - } - else - { - source.SetResult(t.Result); - } - } - }, TaskScheduler.Default); + ExecuteReaderAsyncCallContext args = (ExecuteReaderAsyncCallContext)asyncResult.AsyncState; + return args.Command.EndExecuteReaderAsync(asyncResult); + }, + state: context + ).ContinueWith( + continuationAction: static (Task task) => + { + ExecuteReaderAsyncCallContext context = (ExecuteReaderAsyncCallContext)task.AsyncState; + SqlCommand command = context.Command; + Guid operationId = context.OperationID; + TaskCompletionSource source = context.TaskCompletionSource; + context.Dispose(); + + command.CleanupExecuteReaderAsync(task, source, operationId); + }, + scheduler: TaskScheduler.Default + ); } catch (Exception e) { source.SetException(e); + context?.Dispose(); } return returnedTask; } + private void SetCachedCommandExecuteReaderAsyncContext(ExecuteReaderAsyncCallContext instance) + { + if (_activeConnection?.InnerConnection is SqlInternalConnection sqlInternalConnection) + { + Interlocked.CompareExchange(ref sqlInternalConnection.CachedCommandExecuteReaderAsyncContext, instance, null); + } + } + + private void SetCachedCommandExecuteNonQueryAsyncContext(ExecuteNonQueryAsyncCallContext instance) + { + if (_activeConnection?.InnerConnection is SqlInternalConnection sqlInternalConnection) + { + Interlocked.CompareExchange(ref sqlInternalConnection.CachedCommandExecuteNonQueryAsyncContext, instance, null); + } + } + + private void SetCachedCommandExecuteXmlReaderContext(ExecuteXmlReaderAsyncCallContext instance) + { + if (_activeConnection?.InnerConnection is SqlInternalConnection sqlInternalConnection) + { + Interlocked.CompareExchange(ref sqlInternalConnection.CachedCommandExecuteXmlReaderAsyncContext, instance, null); + } + } + /// public override Task ExecuteScalarAsync(CancellationToken cancellationToken) => // Do not use retry logic here as internal call to ExecuteReaderAsync handles retry logic. @@ -3281,7 +3123,9 @@ private Task InternalExecuteScalarAsync(CancellationToken cancellationTo // exception thrown by Dispose... source.SetException(e); } - }, TaskScheduler.Default); + }, + TaskScheduler.Default + ); } return source.Task; }, TaskScheduler.Default).Unwrap(); @@ -3306,6 +3150,7 @@ private Task InternalExecuteXmlReaderAsync(CancellationToken cancella { SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current); SqlConnection.ExecutePermission.Demand(); + Guid operationId = Guid.Empty; // connection can be used as state in RegisterForConnectionCloseNotification continuation // to avoid an allocation so use it as the state value if possible but it can be changed if @@ -3323,31 +3168,45 @@ private Task InternalExecuteXmlReaderAsync(CancellationToken cancella registration = cancellationToken.Register(s_cancelIgnoreFailure, this); } + ExecuteXmlReaderAsyncCallContext context = null; + if (_activeConnection?.InnerConnection is SqlInternalConnection sqlInternalConnection) + { + context = Interlocked.Exchange(ref sqlInternalConnection.CachedCommandExecuteXmlReaderAsyncContext, null); + } + if (context is null) + { + context = new ExecuteXmlReaderAsyncCallContext(); + } + context.Set(this, source, registration, operationId); + Task returnedTask = source.Task; try { returnedTask = RegisterForConnectionCloseNotification(returnedTask); - Task.Factory.FromAsync(BeginExecuteXmlReaderAsync, EndExecuteXmlReaderAsync, null).ContinueWith((t) => - { - registration.Dispose(); - if (t.IsFaulted) + Task.Factory.FromAsync( + beginMethod: static (AsyncCallback callback, object stateObject) => { - Exception e = t.Exception.InnerException; - source.SetException(e); - } - else + return ((ExecuteXmlReaderAsyncCallContext)stateObject).Command.BeginExecuteXmlReaderAsync(callback, stateObject); + }, + endMethod: static (IAsyncResult asyncResult) => { - if (t.IsCanceled) - { - source.SetCanceled(); - } - else - { - source.SetResult(t.Result); - } - } - }, TaskScheduler.Default); + return ((ExecuteXmlReaderAsyncCallContext)asyncResult.AsyncState).Command.EndExecuteXmlReaderAsync(asyncResult); + }, + state: context + ).ContinueWith( + static (Task task) => + { + ExecuteXmlReaderAsyncCallContext context = (ExecuteXmlReaderAsyncCallContext)task.AsyncState; + SqlCommand command = context.Command; + Guid operationId = context.OperationID; + TaskCompletionSource source = context.TaskCompletionSource; + context.Dispose(); + + command.CleanupAfterExecuteXmlReaderAsync(task, source, operationId); + }, + TaskScheduler.Default + ); } catch (Exception e) { @@ -3357,6 +3216,26 @@ private Task InternalExecuteXmlReaderAsync(CancellationToken cancella return returnedTask; } + private void CleanupAfterExecuteXmlReaderAsync(Task task, TaskCompletionSource source, Guid operationId) + { + if (task.IsFaulted) + { + Exception e = task.Exception.InnerException; + source.SetException(e); + } + else + { + if (task.IsCanceled) + { + source.SetCanceled(); + } + else + { + source.SetResult(task.Result); + } + } + } + /// public void RegisterColumnEncryptionKeyStoreProvidersOnCommand(IDictionary customProviders) { @@ -3553,7 +3432,7 @@ internal void DeriveParameters() // Use common parser for SqlClient and OleDb - parse into 4 parts - Server, Catalog, Schema, ProcedureName string[] parsedSProc = MultipartIdentifier.ParseMultipartIdentifier(CommandText, "[\"", "]\"", Strings.SQL_SqlCommandCommandText, false); - if (parsedSProc[3] == null || ADP.IsEmpty(parsedSProc[3])) + if (parsedSProc[3] == null || string.IsNullOrEmpty(parsedSProc[3])) { throw ADP.NoStoredProcedureExists(CommandText); } @@ -3567,14 +3446,14 @@ internal void DeriveParameters() // [user server, if provided].[user catalog, else current database].[sys if 2005, else blank].[sp_procedure_params_rowset] // Server - pass only if user provided. - if (!ADP.IsEmpty(parsedSProc[0])) + if (!string.IsNullOrEmpty(parsedSProc[0])) { SqlCommandSet.BuildStoredProcedureName(cmdText, parsedSProc[0]); cmdText.Append("."); } // Catalog - pass user provided, otherwise use current database. - if (ADP.IsEmpty(parsedSProc[1])) + if (string.IsNullOrEmpty(parsedSProc[1])) { parsedSProc[1] = Connection.Database; } @@ -3595,16 +3474,8 @@ internal void DeriveParameters() } else { - if (this.Connection.Is2005OrNewer) - { - // Procedure - [sp_procedure_params_managed] - cmdText.Append("[sys].[").Append(TdsEnums.SP_PARAMS_MANAGED).Append("]"); - } - else - { - // Procedure - [sp_procedure_params_rowset] - cmdText.Append(".[").Append(TdsEnums.SP_PARAMS).Append("]"); - } + // Procedure - [sp_procedure_params_managed] + cmdText.Append("[sys].[").Append(TdsEnums.SP_PARAMS_MANAGED).Append("]"); colNames = PreSql2008ProcParamsNames; useManagedDataType = false; @@ -3632,7 +3503,7 @@ internal void DeriveParameters() param.Value = groupNumber; } - if (!ADP.IsEmpty(parsedSProc[2])) + if (!string.IsNullOrEmpty(parsedSProc[2])) { // SchemaName is 3rd element in parsed array SqlParameter param = paramsCmd.Parameters.Add(new SqlParameter("@procedure_schema", SqlDbType.NVarChar, 255)); param.Value = UnquoteProcedurePart(parsedSProc[2]); @@ -3721,9 +3592,6 @@ internal void DeriveParameters() // type name for Udt if (SqlDbType.Udt == p.SqlDbType) { - - Debug.Assert(this._activeConnection.Is2005OrNewer, "Invalid datatype token received from pre-2005 server"); - string udtTypeName; if (useManagedDataType) { @@ -3791,7 +3659,6 @@ internal void DeriveParameters() } finally { - TdsParser.ReliabilitySection.Assert("unreliable call to DeriveParameters"); // you need to setup for a thread abort somewhere before you call this method if (processFinallyBlock) { r?.Close(); @@ -3848,19 +3715,16 @@ private void CheckNotificationStateAndAutoEnlist() // present. If so, auto enlist to the dependency ID given in the context data. if (NotificationAutoEnlist) { - if (_activeConnection.Is2005OrNewer) - { // Only supported for 2005... - string notifyContext = SqlNotificationContext(); - if (!ADP.IsEmpty(notifyContext)) - { - // Map to dependency by ID set in context data. - SqlDependency dependency = SqlDependencyPerAppDomainDispatcher.SingletonInstance.LookupDependencyEntry(notifyContext); + string notifyContext = SqlNotificationContext(); + if (!string.IsNullOrEmpty(notifyContext)) + { + // Map to dependency by ID set in context data. + SqlDependency dependency = SqlDependencyPerAppDomainDispatcher.SingletonInstance.LookupDependencyEntry(notifyContext); - if (dependency != null) - { - // Add this command to the dependency. - dependency.AddCommandDependency(this); - } + if (dependency != null) + { + // Add this command to the dependency. + dependency.AddCommandDependency(this); } } } @@ -4012,7 +3876,6 @@ private Task RunExecuteNonQueryTds(string methodName, bool async, int timeout, b } finally { - TdsParser.ReliabilitySection.Assert("unreliable call to RunExecuteNonQueryTds"); // you need to setup for a thread abort somewhere before you call this method if (processFinallyBlock && !async) { // When executing Async, we need to keep the _stateObj alive... @@ -4022,84 +3885,6 @@ private Task RunExecuteNonQueryTds(string methodName, bool async, int timeout, b return null; } - // Smi-specific logic for ExecuteNonQuery - private void RunExecuteNonQuerySmi(bool sendToPipe) - { - SqlInternalConnectionSmi innerConnection = InternalSmiConnection; - - // Set it up, process all of the events, and we're done! - SmiRequestExecutor requestExecutor = null; - try - { - requestExecutor = SetUpSmiRequest(innerConnection); - SmiExecuteType execType; - if (sendToPipe) - execType = SmiExecuteType.ToPipe; - else - execType = SmiExecuteType.NonQuery; - - - SmiEventStream eventStream = null; - // Don't need a CER here because caller already has one that will doom the - // connection if it's a finally-skipping type of problem. - bool processFinallyBlock = true; - try - { - long transactionId; - Transaction transaction; - innerConnection.GetCurrentTransactionPair(out transactionId, out transaction); - - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, innerConnection={1}, transactionId=0x{2}, cmdBehavior={3}.", ObjectID, innerConnection.ObjectID, transactionId, (int)CommandBehavior.Default); - - if (SmiContextFactory.Instance.NegotiatedSmiVersion >= SmiContextFactory.Sql2008Version) - { - eventStream = requestExecutor.Execute( - innerConnection.SmiConnection, - transactionId, - transaction, - CommandBehavior.Default, - execType); - } - else - { - eventStream = requestExecutor.Execute( - innerConnection.SmiConnection, - transactionId, - CommandBehavior.Default, - execType); - } - - while (eventStream.HasEvents) - { - eventStream.ProcessEvent(EventSink); - } - } - catch (Exception e) - { - processFinallyBlock = ADP.IsCatchableExceptionType(e); - throw; - } - finally - { - TdsParser.ReliabilitySection.Assert("unreliable call to RunExecuteNonQuerySmi"); // you need to setup for a thread abort somewhere before you call this method - if (eventStream != null && processFinallyBlock) - { - eventStream.Close(EventSink); - } - } - - EventSink.ProcessMessagesAndThrow(); - } - finally - { - if (requestExecutor != null) - { - requestExecutor.Close(EventSink); - EventSink.ProcessMessagesAndThrow(ignoreNonFatalMessages: true); - } - } - } - /// /// Resets the encryption related state of the command object and each of the parameters. /// BatchRPC doesn't need special handling to cleanup the state of each RPC object and its parameters since a new RPC object and @@ -4231,62 +4016,127 @@ private void PrepareForTransparentEncryption(CommandBehavior cmdBehavior, bool r RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); try { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); - try + // Fetch the encryption information that applies to any of the input parameters. + describeParameterEncryptionDataReader = TryFetchInputParameterEncryptionInfo(timeout, + async, + asyncWrite, + out describeParameterEncryptionNeeded, + out fetchInputParameterEncryptionInfoTask, + out describeParameterEncryptionRpcOriginalRpcMap, + inRetry); + + Debug.Assert(describeParameterEncryptionNeeded || describeParameterEncryptionDataReader == null, + "describeParameterEncryptionDataReader should be null if we don't need to request describe parameter encryption request."); + + Debug.Assert(fetchInputParameterEncryptionInfoTask == null || async, + "Task returned by TryFetchInputParameterEncryptionInfo, when in sync mode, in PrepareForTransparentEncryption."); + + Debug.Assert((describeParameterEncryptionRpcOriginalRpcMap != null) == _batchRPCMode, + "describeParameterEncryptionRpcOriginalRpcMap can be non-null if and only if it is in _batchRPCMode."); + + // If we didn't have parameters, we can fall back to regular code path, by simply returning. + if (!describeParameterEncryptionNeeded) { - // Fetch the encryption information that applies to any of the input parameters. - describeParameterEncryptionDataReader = TryFetchInputParameterEncryptionInfo(timeout, - async, - asyncWrite, - out describeParameterEncryptionNeeded, - out fetchInputParameterEncryptionInfoTask, - out describeParameterEncryptionRpcOriginalRpcMap, - inRetry); + Debug.Assert(fetchInputParameterEncryptionInfoTask == null, + "fetchInputParameterEncryptionInfoTask should not be set if describe parameter encryption is not needed."); + + Debug.Assert(describeParameterEncryptionDataReader == null, + "SqlDataReader created for describe parameter encryption params when it is not needed."); - Debug.Assert(describeParameterEncryptionNeeded || describeParameterEncryptionDataReader == null, - "describeParameterEncryptionDataReader should be null if we don't need to request describe parameter encryption request."); + return; + } - Debug.Assert(fetchInputParameterEncryptionInfoTask == null || async, - "Task returned by TryFetchInputParameterEncryptionInfo, when in sync mode, in PrepareForTransparentEncryption."); + // If we are in async execution, we need to decrement our async count on exception. + decrementAsyncCountInFinallyBlock = async; - Debug.Assert((describeParameterEncryptionRpcOriginalRpcMap != null) == _batchRPCMode, - "describeParameterEncryptionRpcOriginalRpcMap can be non-null if and only if it is in _batchRPCMode."); + Debug.Assert(describeParameterEncryptionDataReader != null, + "describeParameterEncryptionDataReader should not be null, as it is required to get results of describe parameter encryption."); - // If we didn't have parameters, we can fall back to regular code path, by simply returning. - if (!describeParameterEncryptionNeeded) + // Fire up another task to read the results of describe parameter encryption + if (fetchInputParameterEncryptionInfoTask != null) + { + // Mark that we should not process the finally block since we have async execution pending. + // Note that this should be done outside the task's continuation delegate. + processFinallyBlock = false; + returnTask = AsyncHelper.CreateContinuationTask(fetchInputParameterEncryptionInfoTask, () => { - Debug.Assert(fetchInputParameterEncryptionInfoTask == null, - "fetchInputParameterEncryptionInfoTask should not be set if describe parameter encryption is not needed."); + bool processFinallyBlockAsync = true; + bool decrementAsyncCountInFinallyBlockAsync = true; - Debug.Assert(describeParameterEncryptionDataReader == null, - "SqlDataReader created for describe parameter encryption params when it is not needed."); + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + // Check for any exceptions on network write, before reading. + CheckThrowSNIException(); - return; - } + // If it is async, then TryFetchInputParameterEncryptionInfo-> RunExecuteReaderTds would have incremented the async count. + // Decrement it when we are about to complete async execute reader. + SqlInternalConnectionTds internalConnectionTds = _activeConnection.GetOpenTdsConnection(); + if (internalConnectionTds != null) + { + internalConnectionTds.DecrementAsyncCount(); + decrementAsyncCountInFinallyBlockAsync = false; + } - // If we are in async execution, we need to decrement our async count on exception. - decrementAsyncCountInFinallyBlock = async; + // Complete executereader. + describeParameterEncryptionDataReader = CompleteAsyncExecuteReader(forDescribeParameterEncryption: true); + Debug.Assert(_stateObj == null, "non-null state object in PrepareForTransparentEncryption."); - Debug.Assert(describeParameterEncryptionDataReader != null, - "describeParameterEncryptionDataReader should not be null, as it is required to get results of describe parameter encryption."); + // Read the results of describe parameter encryption. + ReadDescribeEncryptionParameterResults( + describeParameterEncryptionDataReader, + describeParameterEncryptionRpcOriginalRpcMap, + inRetry); - // Fire up another task to read the results of describe parameter encryption - if (fetchInputParameterEncryptionInfoTask != null) +#if DEBUG + // Failpoint to force the thread to halt to simulate cancellation of SqlCommand. + if (_sleepAfterReadDescribeEncryptionParameterResults) + { + Thread.Sleep(10000); + } +#endif //DEBUG + } + catch (Exception e) + { + processFinallyBlockAsync = ADP.IsCatchableExceptionType(e); + throw; + } + finally + { + PrepareTransparentEncryptionFinallyBlock(closeDataReader: processFinallyBlockAsync, + decrementAsyncCount: decrementAsyncCountInFinallyBlockAsync, + clearDataStructures: processFinallyBlockAsync, + wasDescribeParameterEncryptionNeeded: describeParameterEncryptionNeeded, + describeParameterEncryptionRpcOriginalRpcMap: describeParameterEncryptionRpcOriginalRpcMap, + describeParameterEncryptionDataReader: describeParameterEncryptionDataReader); + } + }, + onFailure: ((exception) => + { + if (_cachedAsyncState != null) + { + _cachedAsyncState.ResetAsyncState(); + } + if (exception != null) + { + throw exception; + } + })); + + decrementAsyncCountInFinallyBlock = false; + } + else + { + // If it was async, ending the reader is still pending. + if (async) { // Mark that we should not process the finally block since we have async execution pending. // Note that this should be done outside the task's continuation delegate. processFinallyBlock = false; - returnTask = AsyncHelper.CreateContinuationTask(fetchInputParameterEncryptionInfoTask, () => + returnTask = Task.Run(() => { bool processFinallyBlockAsync = true; bool decrementAsyncCountInFinallyBlockAsync = true; @@ -4294,47 +4144,32 @@ private void PrepareForTransparentEncryption(CommandBehavior cmdBehavior, bool r RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySectionAsync = new TdsParser.ReliabilitySection(); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySectionAsync.Start(); -#endif //DEBUG - // Check for any exceptions on network write, before reading. - CheckThrowSNIException(); - // If it is async, then TryFetchInputParameterEncryptionInfo-> RunExecuteReaderTds would have incremented the async count. - // Decrement it when we are about to complete async execute reader. - SqlInternalConnectionTds internalConnectionTds = _activeConnection.GetOpenTdsConnection(); - if (internalConnectionTds != null) - { - internalConnectionTds.DecrementAsyncCount(); - decrementAsyncCountInFinallyBlockAsync = false; - } + // Check for any exceptions on network write, before reading. + CheckThrowSNIException(); - // Complete executereader. - describeParameterEncryptionDataReader = CompleteAsyncExecuteReader(forDescribeParameterEncryption: true); - Debug.Assert(_stateObj == null, "non-null state object in PrepareForTransparentEncryption."); + // If it is async, then TryFetchInputParameterEncryptionInfo-> RunExecuteReaderTds would have incremented the async count. + // Decrement it when we are about to complete async execute reader. + SqlInternalConnectionTds internalConnectionTds = _activeConnection.GetOpenTdsConnection(); + if (internalConnectionTds != null) + { + internalConnectionTds.DecrementAsyncCount(); + decrementAsyncCountInFinallyBlockAsync = false; + } - // Read the results of describe parameter encryption. - ReadDescribeEncryptionParameterResults( - describeParameterEncryptionDataReader, - describeParameterEncryptionRpcOriginalRpcMap, - inRetry); + // Complete executereader. + describeParameterEncryptionDataReader = CompleteAsyncExecuteReader(forDescribeParameterEncryption: true); + Debug.Assert(_stateObj == null, "non-null state object in PrepareForTransparentEncryption."); + // Read the results of describe parameter encryption. + ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap, inRetry); #if DEBUG - // Failpoint to force the thread to halt to simulate cancellation of SqlCommand. - if (_sleepAfterReadDescribeEncryptionParameterResults) - { - Thread.Sleep(10000); - } - } - finally + // Failpoint to force the thread to halt to simulate cancellation of SqlCommand. + if (_sleepAfterReadDescribeEncryptionParameterResults) { - tdsReliabilitySectionAsync.Stop(); + Thread.Sleep(10000); } -#endif //DEBUG +#endif } catch (Exception e) { @@ -4350,134 +4185,41 @@ private void PrepareForTransparentEncryption(CommandBehavior cmdBehavior, bool r describeParameterEncryptionRpcOriginalRpcMap: describeParameterEncryptionRpcOriginalRpcMap, describeParameterEncryptionDataReader: describeParameterEncryptionDataReader); } - }, - onFailure: ((exception) => - { - if (_cachedAsyncState != null) - { - _cachedAsyncState.ResetAsyncState(); - } - if (exception != null) - { - throw exception; - } - })); + }); decrementAsyncCountInFinallyBlock = false; } else { - // If it was async, ending the reader is still pending. - if (async) - { - // Mark that we should not process the finally block since we have async execution pending. - // Note that this should be done outside the task's continuation delegate. - processFinallyBlock = false; - returnTask = Task.Run(() => - { - bool processFinallyBlockAsync = true; - bool decrementAsyncCountInFinallyBlockAsync = true; - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySectionAsync = new TdsParser.ReliabilitySection(); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySectionAsync.Start(); -#endif //DEBUG - - // Check for any exceptions on network write, before reading. - CheckThrowSNIException(); - - // If it is async, then TryFetchInputParameterEncryptionInfo-> RunExecuteReaderTds would have incremented the async count. - // Decrement it when we are about to complete async execute reader. - SqlInternalConnectionTds internalConnectionTds = _activeConnection.GetOpenTdsConnection(); - if (internalConnectionTds != null) - { - internalConnectionTds.DecrementAsyncCount(); - decrementAsyncCountInFinallyBlockAsync = false; - } - - // Complete executereader. - describeParameterEncryptionDataReader = CompleteAsyncExecuteReader(forDescribeParameterEncryption: true); - Debug.Assert(_stateObj == null, "non-null state object in PrepareForTransparentEncryption."); - - // Read the results of describe parameter encryption. - ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap, inRetry); -#if DEBUG - // Failpoint to force the thread to halt to simulate cancellation of SqlCommand. - if (_sleepAfterReadDescribeEncryptionParameterResults) - { - Thread.Sleep(10000); - } -#endif -#if DEBUG - } - finally - { - tdsReliabilitySectionAsync.Stop(); - } -#endif //DEBUG - } - catch (Exception e) - { - processFinallyBlockAsync = ADP.IsCatchableExceptionType(e); - throw; - } - finally - { - PrepareTransparentEncryptionFinallyBlock(closeDataReader: processFinallyBlockAsync, - decrementAsyncCount: decrementAsyncCountInFinallyBlockAsync, - clearDataStructures: processFinallyBlockAsync, - wasDescribeParameterEncryptionNeeded: describeParameterEncryptionNeeded, - describeParameterEncryptionRpcOriginalRpcMap: describeParameterEncryptionRpcOriginalRpcMap, - describeParameterEncryptionDataReader: describeParameterEncryptionDataReader); - } - }); - - decrementAsyncCountInFinallyBlock = false; - } - else - { - // For synchronous execution, read the results of describe parameter encryption here. - ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap, inRetry); - } + // For synchronous execution, read the results of describe parameter encryption here. + ReadDescribeEncryptionParameterResults(describeParameterEncryptionDataReader, describeParameterEncryptionRpcOriginalRpcMap, inRetry); + } #if DEBUG - // Failpoint to force the thread to halt to simulate cancellation of SqlCommand. - if (_sleepAfterReadDescribeEncryptionParameterResults) - { - Thread.Sleep(10000); - } -#endif + // Failpoint to force the thread to halt to simulate cancellation of SqlCommand. + if (_sleepAfterReadDescribeEncryptionParameterResults) + { + Thread.Sleep(10000); } +#endif } - catch (Exception e) - { - processFinallyBlock = ADP.IsCatchableExceptionType(e); - exceptionCaught = true; - throw; - } - finally - { - // Free up the state only for synchronous execution. For asynchronous execution, free only if there was an exception. - PrepareTransparentEncryptionFinallyBlock(closeDataReader: (processFinallyBlock && !async) || exceptionCaught, - decrementAsyncCount: decrementAsyncCountInFinallyBlock && exceptionCaught, - clearDataStructures: (processFinallyBlock && !async) || exceptionCaught, - wasDescribeParameterEncryptionNeeded: describeParameterEncryptionNeeded, - describeParameterEncryptionRpcOriginalRpcMap: describeParameterEncryptionRpcOriginalRpcMap, - describeParameterEncryptionDataReader: describeParameterEncryptionDataReader); - } } -#if DEBUG + catch (Exception e) + { + processFinallyBlock = ADP.IsCatchableExceptionType(e); + exceptionCaught = true; + throw; + } finally { - tdsReliabilitySection.Stop(); + // Free up the state only for synchronous execution. For asynchronous execution, free only if there was an exception. + PrepareTransparentEncryptionFinallyBlock(closeDataReader: (processFinallyBlock && !async) || exceptionCaught, + decrementAsyncCount: decrementAsyncCountInFinallyBlock && exceptionCaught, + clearDataStructures: (processFinallyBlock && !async) || exceptionCaught, + wasDescribeParameterEncryptionNeeded: describeParameterEncryptionNeeded, + describeParameterEncryptionRpcOriginalRpcMap: describeParameterEncryptionRpcOriginalRpcMap, + describeParameterEncryptionDataReader: describeParameterEncryptionDataReader); } -#endif //DEBUG } catch (System.OutOfMemoryException e) { @@ -5163,59 +4905,83 @@ internal SqlDataReader RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); + SqlStatistics statistics = Statistics; + if (statistics != null) { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); - SqlStatistics statistics = Statistics; - if (statistics != null) + if ((!this.IsDirty && this.IsPrepared && !_hiddenPrepare) + || (this.IsPrepared && _execType == EXECTYPE.PREPAREPENDING)) { - if ((!this.IsDirty && this.IsPrepared && !_hiddenPrepare) - || (this.IsPrepared && _execType == EXECTYPE.PREPAREPENDING)) - { - statistics.SafeIncrement(ref statistics._preparedExecs); - } - else - { - statistics.SafeIncrement(ref statistics._unpreparedExecs); - } + statistics.SafeIncrement(ref statistics._preparedExecs); + } + else + { + statistics.SafeIncrement(ref statistics._unpreparedExecs); } + } + + // Reset the encryption related state of the command and its parameters. + ResetEncryptionState(); + + if (IsColumnEncryptionEnabled) + { + Task returnTask = null; + PrepareForTransparentEncryption(cmdBehavior, returnStream, async, timeout, completion, out returnTask, asyncWrite && async, out usedCache, inRetry); + Debug.Assert(usedCache || (async == (returnTask != null)), @"if we didn't use the cache, returnTask should be null if and only if async is false."); - // Reset the encryption related state of the command and its parameters. - ResetEncryptionState(); + long firstAttemptStart = ADP.TimerCurrent(); - if (_activeConnection.IsContextConnection) + try { - return RunExecuteReaderSmi(cmdBehavior, runBehavior, returnStream); + return RunExecuteReaderTdsWithTransparentParameterEncryption(cmdBehavior, runBehavior, returnStream, async, timeout, out task, asyncWrite && async, inRetry: inRetry, ds: null, + describeParameterEncryptionRequest: false, describeParameterEncryptionTask: returnTask); } - else if (IsColumnEncryptionEnabled) + + catch (EnclaveDelegate.RetryableEnclaveQueryExecutionException) { - Task returnTask = null; - PrepareForTransparentEncryption(cmdBehavior, returnStream, async, timeout, completion, out returnTask, asyncWrite && async, out usedCache, inRetry); - Debug.Assert(usedCache || (async == (returnTask != null)), @"if we didn't use the cache, returnTask should be null if and only if async is false."); + if (inRetry) + { + throw; + } - long firstAttemptStart = ADP.TimerCurrent(); + // Retry if the command failed with appropriate error. + // First invalidate the entry from the cache, so that we refresh our encryption MD. + SqlQueryMetadataCache.GetInstance().InvalidateCacheEntry(this); - try + InvalidateEnclaveSession(); + + return RunExecuteReader(cmdBehavior, runBehavior, returnStream, method, completion, TdsParserStaticMethods.GetRemainingTimeout(timeout, firstAttemptStart), out task, out usedCache, async, inRetry: true); + } + + catch (SqlException ex) + { + // We only want to retry once, so don't retry if we are already in retry. + // If we didn't use the cache, we don't want to retry. + if (inRetry || (!usedCache && !ShouldUseEnclaveBasedWorkflow)) { - return RunExecuteReaderTdsWithTransparentParameterEncryption(cmdBehavior, runBehavior, returnStream, async, timeout, out task, asyncWrite && async, inRetry: inRetry, ds: null, - describeParameterEncryptionRequest: false, describeParameterEncryptionTask: returnTask); + throw; } - catch (EnclaveDelegate.RetryableEnclaveQueryExecutionException) + bool shouldRetry = false; + + // Check if we have an error indicating that we can retry. + for (int i = 0; i < ex.Errors.Count; i++) { - if (inRetry) + + if ((usedCache && (ex.Errors[i].Number == TdsEnums.TCE_CONVERSION_ERROR_CLIENT_RETRY)) || + (ShouldUseEnclaveBasedWorkflow && (ex.Errors[i].Number == TdsEnums.TCE_ENCLAVE_INVALID_SESSION_HANDLE))) { - throw; + shouldRetry = true; + break; } + } + if (!shouldRetry) + { + throw; + } + else + { // Retry if the command failed with appropriate error. // First invalidate the entry from the cache, so that we refresh our encryption MD. SqlQueryMetadataCache.GetInstance().InvalidateCacheEntry(this); @@ -5224,57 +4990,12 @@ internal SqlDataReader RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior return RunExecuteReader(cmdBehavior, runBehavior, returnStream, method, completion, TdsParserStaticMethods.GetRemainingTimeout(timeout, firstAttemptStart), out task, out usedCache, async, inRetry: true); } - - catch (SqlException ex) - { - // We only want to retry once, so don't retry if we are already in retry. - // If we didn't use the cache, we don't want to retry. - if (inRetry || (!usedCache && !ShouldUseEnclaveBasedWorkflow)) - { - throw; - } - - bool shouldRetry = false; - - // Check if we have an error indicating that we can retry. - for (int i = 0; i < ex.Errors.Count; i++) - { - - if ((usedCache && (ex.Errors[i].Number == TdsEnums.TCE_CONVERSION_ERROR_CLIENT_RETRY)) || - (ShouldUseEnclaveBasedWorkflow && (ex.Errors[i].Number == TdsEnums.TCE_ENCLAVE_INVALID_SESSION_HANDLE))) - { - shouldRetry = true; - break; - } - } - - if (!shouldRetry) - { - throw; - } - else - { - // Retry if the command failed with appropriate error. - // First invalidate the entry from the cache, so that we refresh our encryption MD. - SqlQueryMetadataCache.GetInstance().InvalidateCacheEntry(this); - - InvalidateEnclaveSession(); - - return RunExecuteReader(cmdBehavior, runBehavior, returnStream, method, completion, TdsParserStaticMethods.GetRemainingTimeout(timeout, firstAttemptStart), out task, out usedCache, async, inRetry: true); - } - } - } - else - { - return RunExecuteReaderTds(cmdBehavior, runBehavior, returnStream, async, timeout, out task, asyncWrite && async, inRetry: inRetry); } } -#if DEBUG - finally + else { - tdsReliabilitySection.Stop(); + return RunExecuteReaderTds(cmdBehavior, runBehavior, returnStream, async, timeout, out task, asyncWrite && async, inRetry: inRetry); } -#endif //DEBUG } catch (System.OutOfMemoryException e) { @@ -5559,7 +5280,6 @@ private SqlDataReader RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavi } else if (_execType == EXECTYPE.PREPAREPENDING) { - Debug.Assert(_activeConnection.Is2000, "Invalid attempt to call sp_prepexec on non 7.x server"); rpc = BuildPrepExec(cmdBehavior); // next time through, only do an exec _execType = EXECTYPE.PREPARED; @@ -5575,8 +5295,7 @@ private SqlDataReader RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavi } // if 2000, then set NOMETADATA_UNLESSCHANGED flag - if (_activeConnection.Is2000) - rpc.options = TdsEnums.RPC_NOMETADATA; + rpc.options = TdsEnums.RPC_NOMETADATA; if (returnStream) { SqlClientEventSource.Log.TryTraceEvent(" {0}, Command executed as RPC.", ObjectID); @@ -5589,10 +5308,6 @@ private SqlDataReader RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavi else { Debug.Assert(this.CommandType == System.Data.CommandType.StoredProcedure, "unknown command type!"); - // note: invalid asserts on 2000. On 8.0 (2000) and above a command is ALWAYS prepared - // and IsDirty is always set if there are changes and the command is marked Prepared! - Debug.Assert(Is2000 || !IsPrepared, "RPC should not be prepared!"); - Debug.Assert(Is2000 || !IsDirty, "RPC should not be marked as dirty!"); BuildRPC(inSchema, _parameters, ref rpc); @@ -5668,7 +5383,6 @@ private SqlDataReader RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavi } finally { - TdsParser.ReliabilitySection.Assert("unreliable call to RunExecuteReaderTds"); // you need to setup for a thread abort somewhere before you call this method if (processFinallyBlock && !async) { // When executing async, we need to keep the _stateObj alive... @@ -5680,89 +5394,6 @@ private SqlDataReader RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavi return ds; } - private SqlDataReader RunExecuteReaderSmi(CommandBehavior cmdBehavior, RunBehavior runBehavior, bool returnStream) - { - SqlInternalConnectionSmi innerConnection = InternalSmiConnection; - - SmiEventStream eventStream = null; - SqlDataReader ds = null; - SmiRequestExecutor requestExecutor = null; - try - { - // Set it up, process all of the events, and we're done! - requestExecutor = SetUpSmiRequest(innerConnection); - - long transactionId; - Transaction transaction; - innerConnection.GetCurrentTransactionPair(out transactionId, out transaction); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, innerConnection={1}, transactionId=0x{2}, commandBehavior={(int)cmdBehavior}.", ObjectID, innerConnection.ObjectID, transactionId); - - if (SmiContextFactory.Instance.NegotiatedSmiVersion >= SmiContextFactory.Sql2008Version) - { - eventStream = requestExecutor.Execute( - innerConnection.SmiConnection, - transactionId, - transaction, - cmdBehavior, - SmiExecuteType.Reader - ); - } - else - { - eventStream = requestExecutor.Execute( - innerConnection.SmiConnection, - transactionId, - cmdBehavior, - SmiExecuteType.Reader - ); - } - - if ((runBehavior & RunBehavior.UntilDone) != 0) - { - - // Consume the results - while (eventStream.HasEvents) - { - eventStream.ProcessEvent(EventSink); - } - eventStream.Close(EventSink); - } - - if (returnStream) - { - ds = new SqlDataReaderSmi(eventStream, this, cmdBehavior, innerConnection, EventSink, requestExecutor); - ds.NextResult(); // Position on first set of results - _activeConnection.AddWeakReference(ds, SqlReferenceCollection.DataReaderTag); - } - - EventSink.ProcessMessagesAndThrow(); - } - catch (Exception e) - { - // VSTS 159716 - we do not want to handle ThreadAbort, OutOfMemory or similar critical exceptions - // because the state of used objects might remain invalid in this case - if (!ADP.IsCatchableOrSecurityExceptionType(e)) - { - throw; - } - - if (eventStream != null) - { - eventStream.Close(EventSink); // UNDONE: should cancel instead! - } - - if (requestExecutor != null) - { - requestExecutor.Close(EventSink); - EventSink.ProcessMessagesAndThrow(ignoreNonFatalMessages: true); - } - - throw; - } - - return ds; - } - private SqlDataReader CompleteAsyncExecuteReader(bool isInternal = false, bool forDescribeParameterEncryption = false) { SqlDataReader ds = cachedAsyncState.CachedAsyncReader; // should not be null @@ -5778,7 +5409,6 @@ private SqlDataReader CompleteAsyncExecuteReader(bool isInternal = false, bool f } finally { - TdsParser.ReliabilitySection.Assert("unreliable call to CompleteAsyncExecuteReader"); // you need to setup for a thread abort somewhere before you call this method if (processFinallyBlock) { // Don't reset the state for internal End. The user End will do that eventually. @@ -5984,28 +5614,10 @@ private void ValidateCommand(string method, bool async) RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); - // close any non MARS dead readers, if applicable, and then throw if still busy. - // Throw if we have a live reader on this command - _activeConnection.ValidateConnectionForExecute(method, this); - - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); + // close any non MARS dead readers, if applicable, and then throw if still busy. + // Throw if we have a live reader on this command + _activeConnection.ValidateConnectionForExecute(method, this); } catch (System.OutOfMemoryException e) { @@ -6045,22 +5657,10 @@ private void ValidateCommand(string method, bool async) throw ADP.TransactionConnectionMismatch(); } - if (ADP.IsEmpty(this.CommandText)) + if (string.IsNullOrEmpty(this.CommandText)) { throw ADP.CommandTextRequired(method); } - - // Notification property must be null for pre-2005 connections - if ((Notification != null) && !_activeConnection.Is2005OrNewer) - { - throw SQL.NotificationsRequire2005(); - } - - if ((async) && (_activeConnection.IsContextConnection)) - { - // Async not supported on Context Connections - throw SQL.NotAvailableOnContextConnection(); - } } private void ValidateAsyncCommand() @@ -6107,7 +5707,7 @@ private void GetStateObject(TdsParser parser = null) } TdsParserStateObject stateObj = parser.GetSession(this); - stateObj.StartSession(ObjectID); + stateObj.StartSession(this); _stateObj = stateObj; @@ -6130,26 +5730,8 @@ private void ReliablePutStateObject() RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); - PutStateObject(); - - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_activeConnection); + PutStateObject(); } catch (System.OutOfMemoryException e) { @@ -6452,58 +6034,6 @@ internal void OnReturnValue(SqlReturnValue rec, TdsParserStateObject stateObj) return; } - internal void OnParametersAvailableSmi(SmiParameterMetaData[] paramMetaData, ITypedGettersV3 parameterValues) - { - Debug.Assert(paramMetaData != null); - - for (int index = 0; index < paramMetaData.Length; index++) - { - OnParameterAvailableSmi(paramMetaData[index], parameterValues, index); - } - } - - internal void OnParameterAvailableSmi(SmiParameterMetaData metaData, ITypedGettersV3 parameterValues, int ordinal) - { - if (ParameterDirection.Input != metaData.Direction) - { - string name = null; - if (ParameterDirection.ReturnValue != metaData.Direction) - { - name = metaData.Name; - } - - SqlParameterCollection parameters = GetCurrentParameterCollection(); - int count = GetParameterCount(parameters); - SqlParameter param = GetParameterForOutputValueExtraction(parameters, name, count); - - if (param != null) - { - param.LocaleId = (int)metaData.LocaleId; - param.CompareInfo = metaData.CompareOptions; - SqlBuffer buffer = new SqlBuffer(); - object result; - if (_activeConnection.Is2008OrNewer) - { - result = ValueUtilsSmi.GetOutputParameterV200Smi( - OutParamEventSink, (SmiTypedGetterSetter)parameterValues, ordinal, metaData, _smiRequestContext, buffer); - } - else - { - result = ValueUtilsSmi.GetOutputParameterV3Smi( - OutParamEventSink, parameterValues, ordinal, metaData, _smiRequestContext, buffer); - } - if (result != null) - { - param.Value = result; - } - else - { - param.SetSqlBuffer(buffer); - } - } - } - } - private SqlParameterCollection GetCurrentParameterCollection() { if (_batchRPCMode) @@ -7064,7 +6594,7 @@ internal string BuildParamList(TdsParser parser, SqlParameterCollection paramete if (mt.SqlDbType == SqlDbType.Udt) { string fullTypeName = sqlParam.UdtTypeName; - if (ADP.IsEmpty(fullTypeName)) + if (string.IsNullOrEmpty(fullTypeName)) throw SQL.MustSetUdtTypeNameForUdtParams(); paramList.Append(ParseAndQuoteIdentifier(fullTypeName, true /* is UdtTypeName */)); @@ -7072,7 +6602,7 @@ internal string BuildParamList(TdsParser parser, SqlParameterCollection paramete else if (mt.SqlDbType == SqlDbType.Structured) { string typeName = sqlParam.TypeName; - if (ADP.IsEmpty(typeName)) + if (string.IsNullOrEmpty(typeName)) { throw SQL.MustSetTypeNameForParam(mt.TypeName, sqlParam.GetPrefixedParameterName()); } @@ -7104,14 +6634,7 @@ internal string BuildParamList(TdsParser parser, SqlParameterCollection paramete if (0 == precision) { - if (Is2000) - { - precision = TdsEnums.DEFAULT_NUMERIC_PRECISION; - } - else - { - precision = TdsEnums.SQL70_DEFAULT_NUMERIC_PRECISION; - } + precision = TdsEnums.DEFAULT_NUMERIC_PRECISION; } paramList.Append(precision); @@ -7127,6 +6650,13 @@ internal string BuildParamList(TdsParser parser, SqlParameterCollection paramete paramList.Append(scale); paramList.Append(')'); } + else if (mt.SqlDbType == SqlDbTypeExtensions.Vector) + { + var sqlVectorProps = (ISqlVector)sqlParam.Value; + paramList.Append('('); + paramList.Append(sqlVectorProps.Length); + paramList.Append(')'); + } else if (!mt.IsFixed && !mt.IsLong && mt.SqlDbType != SqlDbType.Timestamp && mt.SqlDbType != SqlDbType.Udt && SqlDbType.Structured != mt.SqlDbType) { int size = sqlParam.Size; @@ -7519,190 +7049,6 @@ internal SqlException GetErrors(int commandIndex) return result; } - // Allocates and initializes a new SmiRequestExecutor based on the current command state - private SmiRequestExecutor SetUpSmiRequest(SqlInternalConnectionSmi innerConnection) - { - - // General Approach To Ensure Security of Marshalling: - // Only touch each item in the command once - // (i.e. only grab a reference to each param once, only - // read the type from that param once, etc.). The problem is - // that if the user changes something on the command in the - // middle of marshaling, it can overwrite the native buffers - // set up. For example, if max length is used to allocate - // buffers, but then re-read from the parameter to truncate - // strings, the user could extend the length and overwrite - // the buffer. - - if (Notification != null) - { - throw SQL.NotificationsNotAvailableOnContextConnection(); - } - - SmiParameterMetaData[] requestMetaData = null; - ParameterPeekAheadValue[] peekAheadValues = null; - - // Length of rgMetadata becomes *the* official count of parameters to use, - // don't rely on Parameters.Count after this point, as the user could change it. - int count = GetParameterCount(Parameters); - if (0 < count) - { - requestMetaData = new SmiParameterMetaData[count]; - peekAheadValues = new ParameterPeekAheadValue[count]; - - // set up the metadata - for (int index = 0; index < count; index++) - { - SqlParameter param = Parameters[index]; - param.Validate(index, CommandType.StoredProcedure == CommandType); - requestMetaData[index] = param.MetaDataForSmi(out peekAheadValues[index]); - - // Check for valid type for version negotiated - if (!innerConnection.Is2008OrNewer) - { - MetaType mt = MetaType.GetMetaTypeFromSqlDbType(requestMetaData[index].SqlDbType, requestMetaData[index].IsMultiValued); - if (!mt.Is90Supported) - { - throw ADP.VersionDoesNotSupportDataType(mt.TypeName); - } - } - } - } - - // Allocate the new request - CommandType cmdType = CommandType; - _smiRequestContext = innerConnection.InternalContext; - SmiRequestExecutor requestExecutor = _smiRequestContext.CreateRequestExecutor( - CommandText, - cmdType, - requestMetaData, - EventSink - ); - - // deal with errors - EventSink.ProcessMessagesAndThrow(); - - // Now assign param values - for (int index = 0; index < count; index++) - { - if (ParameterDirection.Output != requestMetaData[index].Direction && - ParameterDirection.ReturnValue != requestMetaData[index].Direction) - { - SqlParameter param = Parameters[index]; - // going back to command for parameter is ok, since we'll only pick up values now. - object value = param.GetCoercedValue(); - if (value is XmlDataFeed && requestMetaData[index].SqlDbType != SqlDbType.Xml) - { - value = MetaType.GetStringFromXml(((XmlDataFeed)value)._source); - } - ExtendedClrTypeCode typeCode = MetaDataUtilsSmi.DetermineExtendedTypeCodeForUseWithSqlDbType(requestMetaData[index].SqlDbType, requestMetaData[index].IsMultiValued, value, null /* parameters don't use CLR Type for UDTs */, SmiContextFactory.Instance.NegotiatedSmiVersion); - - // Handle null reference as special case for parameters - if (CommandType.StoredProcedure == cmdType && - ExtendedClrTypeCode.Empty == typeCode) - { - requestExecutor.SetDefault(index); - } - else - { - // SQLBU 402391 & 403631: Exception to prevent Parameter.Size data corruption cases from working. - // This should be temporary until changing to correct behavior can be safely implemented. - // initial size criteria is the same for all affected types - // NOTE: assumes size < -1 is handled by SqlParameter.Size setter - int size = param.Size; - if (size != 0 && size != SmiMetaData.UnlimitedMaxLengthIndicator && !param.SizeInferred) - { - switch (requestMetaData[index].SqlDbType) - { - case SqlDbType.Image: - case SqlDbType.Text: - if (size != Int32.MaxValue) - { - throw SQL.ParameterSizeRestrictionFailure(index); - } - break; - - case SqlDbType.NText: - if (size != Int32.MaxValue / 2) - { - throw SQL.ParameterSizeRestrictionFailure(index); - } - break; - - case SqlDbType.VarBinary: - case SqlDbType.VarChar: - // Allow size==Int32.MaxValue because of DeriveParameters - if (size > 0 && size != Int32.MaxValue && requestMetaData[index].MaxLength == SmiMetaData.UnlimitedMaxLengthIndicator) - { - throw SQL.ParameterSizeRestrictionFailure(index); - } - break; - - case SqlDbType.NVarChar: - // Allow size==Int32.MaxValue/2 because of DeriveParameters - if (size > 0 && size != Int32.MaxValue / 2 && requestMetaData[index].MaxLength == SmiMetaData.UnlimitedMaxLengthIndicator) - { - throw SQL.ParameterSizeRestrictionFailure(index); - } - break; - - case SqlDbType.Timestamp: - // Size limiting for larger values will happen due to MaxLength - if (size < SmiMetaData.DefaultTimestamp.MaxLength) - { - throw SQL.ParameterSizeRestrictionFailure(index); - } - break; - - case SqlDbType.Variant: - // Variant problems happen when Size is less than maximums for character and binary values - // Size limiting for larger values will happen due to MaxLength - // NOTE: assumes xml and udt types are handled in parameter value coercion - // since server does not allow these types in a variant - if (value != null) - { - MetaType mt = MetaType.GetMetaTypeFromValue(value); - - if ((mt.IsNCharType && size < SmiMetaData.MaxUnicodeCharacters) || - (mt.IsBinType && size < SmiMetaData.MaxBinaryLength) || - (mt.IsAnsiType && size < SmiMetaData.MaxANSICharacters)) - { - throw SQL.ParameterSizeRestrictionFailure(index); - } - } - break; - - case SqlDbType.Xml: - // Xml is an issue for non-SqlXml types - if (value != null && ExtendedClrTypeCode.SqlXml != typeCode) - { - throw SQL.ParameterSizeRestrictionFailure(index); - } - break; - - // NOTE: Char, NChar, Binary and UDT do not need restricting because they are always 8k or less, - // so the metadata MaxLength will match the Size setting. - - default: - break; - } - } - - if (innerConnection.Is2008OrNewer) - { - ValueUtilsSmi.SetCompatibleValueV200(EventSink, requestExecutor, index, requestMetaData[index], value, typeCode, param.Offset, peekAheadValues[index]); - } - else - { - ValueUtilsSmi.SetCompatibleValue(EventSink, requestExecutor, index, requestMetaData[index], value, typeCode, param.Offset); - } - } - } - } - - return requestExecutor; - } - private void WriteBeginExecuteEvent() { SqlClientEventSource.Log.TryBeginExecuteEvent(ObjectID, Connection?.DataSource, Connection?.Database, CommandText, Connection?.ClientConnectionId); diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs index 931fb96640..f03c58fd15 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -16,15 +16,15 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; -using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Security.Principal; -using System.Text; using System.Threading; using System.Threading.Tasks; +using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; using Microsoft.SqlServer.Server; [assembly: InternalsVisibleTo("System.Data.DataSetExtensions, PublicKey=" + Microsoft.Data.SqlClient.AssemblyRef.EcmaPublicKeyFull)] // DevDiv Bugs 92166 @@ -32,29 +32,51 @@ // New attributes that are designed to work with Microsoft.Data.SqlClient and are publicly documented should be included in future. namespace Microsoft.Data.SqlClient { - using System.Diagnostics.Tracing; - using Microsoft.Data.Common; - /// - [ - DefaultEvent("InfoMessage"), - DesignerCategory("") - ] + [DefaultEvent("InfoMessage")] + [DesignerCategory("")] public sealed partial class SqlConnection : DbConnection, ICloneable { + static private readonly object EventInfoMessage = new object(); - internal bool ForceNewConnection - { - get; - set; - } + private bool _AsyncCommandInProgress; + + // SQLStatistics support + internal SqlStatistics _statistics; + private bool _collectstats; + private bool _fireInfoMessageEventOnUserErrors; // False by default + + // root task associated with current async invocation + private Tuple, Task> _currentCompletion; + + private SqlCredential _credential; + private string _connectionString; + private int _connectRetryCount; + private string _accessToken; // Access Token to be used for token based authentication + + // connection resiliency + private object _reconnectLock = new object(); + internal Task _currentReconnectionTask; + private Task _asyncWaitingForReconnection; // current async task waiting for reconnection in non-MARS connections + private Guid _originalConnectionId = Guid.Empty; + private CancellationTokenSource _reconnectionCancellationSource; + internal SessionData _recoverySessionData; internal bool _suppressStateChangeForReconnection = false; // Do not use for anything else ! Value will be overwritten by CR process + internal WindowsIdentity _lastIdentity; + internal WindowsIdentity _impersonateIdentity; + private int _reconnectCount; - static private readonly object EventInfoMessage = new object(); + // Retry Logic + private SqlRetryLogicBaseProvider _retryLogicProvider; + + // Transient Fault handling flag. This is needed to convey to the downstream mechanism of connection establishment, if Transient Fault handling should be used or not + // The downstream handling of Connection open is the same for idle connection resiliency. Currently we want to apply transient fault handling only to the connections opened + // using SqlConnection.Open() method. + internal bool _applyTransientFaultHandling = false; // System column encryption key store providers are added by default - private static Dictionary s_systemColumnEncryptionKeyStoreProviders + private static readonly Dictionary s_systemColumnEncryptionKeyStoreProviders = new(capacity: 3, comparer: StringComparer.OrdinalIgnoreCase) { { SqlColumnEncryptionCertificateStoreProvider.ProviderName, new SqlColumnEncryptionCertificateStoreProvider() }, @@ -62,12 +84,6 @@ private static Dictionary s_systemC { SqlColumnEncryptionCspProvider.ProviderName, new SqlColumnEncryptionCspProvider() } }; - /// - /// Global custom provider list should be provided by the user. We shallow copy the user supplied dictionary into a ReadOnlyDictionary. - /// Global custom provider list can only be supplied once per application. - /// - private static IReadOnlyDictionary s_globalCustomColumnEncryptionKeyStoreProviders; - /// Instance-level list of custom key store providers. It can be set more than once by the user. private IReadOnlyDictionary _customColumnEncryptionKeyStoreProviders; @@ -79,141 +95,136 @@ private static Dictionary s_systemC // Lock to control setting of s_globalCustomColumnEncryptionKeyStoreProviders private static readonly object s_globalCustomColumnEncryptionKeyProvidersLock = new(); + /// + /// Global custom provider list should be provided by the user. We shallow copy the user supplied dictionary into a ReadOnlyDictionary. + /// Global custom provider list can only be supplied once per application. + /// + private static IReadOnlyDictionary s_globalCustomColumnEncryptionKeyStoreProviders; + /// /// Dictionary object holding trusted key paths for various SQL Servers. /// Key to the dictionary is a SQL Server Name /// IList contains a list of trusted key paths. /// - static private readonly ConcurrentDictionary> _ColumnEncryptionTrustedMasterKeyPaths + private static readonly ConcurrentDictionary> _ColumnEncryptionTrustedMasterKeyPaths = new(concurrencyLevel: 4 * Environment.ProcessorCount /* default value in ConcurrentDictionary*/, capacity: 1, comparer: StringComparer.OrdinalIgnoreCase); - /// - [ - DefaultValue(null), - ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data), - ResDescriptionAttribute(StringsHelper.ResourceNames.TCE_SqlConnection_TrustedColumnMasterKeyPaths), - ] - public static IDictionary> ColumnEncryptionTrustedMasterKeyPaths => _ColumnEncryptionTrustedMasterKeyPaths; - - /// - /// Defines whether query metadata caching is enabled. - /// - static private bool _ColumnEncryptionQueryMetadataCacheEnabled = true; + private bool IsProviderRetriable => SqlConfigurableRetryFactory.IsRetriable(RetryLogicProvider); - /// - [ - DefaultValue(null), - ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data), - ResDescriptionAttribute(StringsHelper.ResourceNames.TCE_SqlConnection_ColumnEncryptionQueryMetadataCacheEnabled), - ] - static public bool ColumnEncryptionQueryMetadataCacheEnabled + /// + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public SqlRetryLogicBaseProvider RetryLogicProvider { get { - return _ColumnEncryptionQueryMetadataCacheEnabled; + if (_retryLogicProvider == null) + { + _retryLogicProvider = SqlConfigurableRetryLogicManager.ConnectionProvider; + } + return _retryLogicProvider; } set { - _ColumnEncryptionQueryMetadataCacheEnabled = value; + _retryLogicProvider = value; } } - /// - /// Defines whether query metadata caching is enabled. - /// - static private TimeSpan _ColumnEncryptionKeyCacheTtl = TimeSpan.FromHours(2); - /// - [ - DefaultValue(null), - ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data), - ResDescriptionAttribute(StringsHelper.ResourceNames.TCE_SqlConnection_ColumnEncryptionKeyCacheTtl), - ] - static public TimeSpan ColumnEncryptionKeyCacheTtl + [DefaultValue(null)] + [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data)] + [ResDescription(StringsHelper.ResourceNames.TCE_SqlConnection_ColumnEncryptionKeyCacheTtl)] + public static TimeSpan ColumnEncryptionKeyCacheTtl { get; set; } = TimeSpan.FromHours(2); + + /// + [DefaultValue(null)] + [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data)] + [ResDescription(StringsHelper.ResourceNames.TCE_SqlConnection_ColumnEncryptionQueryMetadataCacheEnabled)] + public static bool ColumnEncryptionQueryMetadataCacheEnabled { get; set; } = true; + + /// + [DefaultValue(null)] + [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data)] + [ResDescription(StringsHelper.ResourceNames.TCE_SqlConnection_TrustedColumnMasterKeyPaths)] + public static IDictionary> ColumnEncryptionTrustedMasterKeyPaths => _ColumnEncryptionTrustedMasterKeyPaths; + + /// + public SqlConnection(string connectionString) : this(connectionString, null) { - get => _ColumnEncryptionKeyCacheTtl; - set => _ColumnEncryptionKeyCacheTtl = value; } - /// - static public void RegisterColumnEncryptionKeyStoreProviders(IDictionary customProviders) + /// + public SqlConnection(string connectionString, SqlCredential credential) : this() { - ValidateCustomProviders(customProviders); - - lock (s_globalCustomColumnEncryptionKeyProvidersLock) + ConnectionString = connectionString; // setting connection string first so that ConnectionOption is available + if (credential != null) { - // Provider list can only be set once - if (s_globalCustomColumnEncryptionKeyStoreProviders is not null) + // The following checks are necessary as setting Credential property will call CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential + // CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential it will throw InvalidOperationException rather than ArgumentException + // Need to call setter on Credential property rather than setting _credential directly as pool groups need to be checked + SqlConnectionString connectionOptions = (SqlConnectionString)ConnectionOptions; + if (UsesClearUserIdOrPassword(connectionOptions)) { - throw SQL.CanOnlyCallOnce(); + throw ADP.InvalidMixedArgumentOfSecureAndClearCredential(); } - // to prevent conflicts between CEK caches, global providers should not use their own CEK caches - foreach (SqlColumnEncryptionKeyStoreProvider provider in customProviders.Values) + if (UsesIntegratedSecurity(connectionOptions)) { - provider.ColumnEncryptionKeyCacheTtl = new TimeSpan(0); + throw ADP.InvalidMixedArgumentOfSecureCredentialAndIntegratedSecurity(); + } + else if (UsesActiveDirectoryIntegrated(connectionOptions)) + { + throw SQL.SettingCredentialWithIntegratedArgument(); + } + else if (UsesActiveDirectoryInteractive(connectionOptions)) + { + throw SQL.SettingCredentialWithInteractiveArgument(); + } + else if (UsesActiveDirectoryDeviceCodeFlow(connectionOptions)) + { + throw SQL.SettingCredentialWithDeviceFlowArgument(); + } + else if (UsesActiveDirectoryManagedIdentity(connectionOptions)) + { + throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringUtilities.ActiveDirectoryManagedIdentityString); + } + else if (UsesActiveDirectoryMSI(connectionOptions)) + { + throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringUtilities.ActiveDirectoryMSIString); + } + else if (UsesActiveDirectoryDefault(connectionOptions)) + { + throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringUtilities.ActiveDirectoryDefaultString); + } + else if (UsesActiveDirectoryWorkloadIdentity(connectionOptions)) + { + throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringUtilities.ActiveDirectoryWorkloadIdentityString); } - // Create a temporary dictionary and then add items from the provided dictionary. - // Dictionary constructor does shallow copying by simply copying the provider name and provider reference pairs - // in the provided customerProviders dictionary. - Dictionary customColumnEncryptionKeyStoreProviders = - new(customProviders, StringComparer.OrdinalIgnoreCase); - - // Set the dictionary to the ReadOnly dictionary. - s_globalCustomColumnEncryptionKeyStoreProviders = customColumnEncryptionKeyStoreProviders; + Credential = credential; } + // else + // credential == null: we should not set "Credential" as this will do additional validation check and + // checking pool groups which is not necessary. All necessary operation is already done by calling "ConnectionString = connectionString" } - /// - public void RegisterColumnEncryptionKeyStoreProvidersOnConnection(IDictionary customProviders) - { - ValidateCustomProviders(customProviders); - - // Create a temporary dictionary and then add items from the provided dictionary. - // Dictionary constructor does shallow copying by simply copying the provider name and provider reference pairs - // in the provided customerProviders dictionary. - Dictionary customColumnEncryptionKeyStoreProviders = - new(customProviders, StringComparer.OrdinalIgnoreCase); - - // Set the dictionary to the ReadOnly dictionary. - // This method can be called more than once. Re-registering a new collection will replace the - // old collection of providers. - _customColumnEncryptionKeyStoreProviders = customColumnEncryptionKeyStoreProviders; - } - - private static void ValidateCustomProviders(IDictionary customProviders) + private SqlConnection(SqlConnection connection) { - // Throw when the provided dictionary is null. - if (customProviders is null) + GC.SuppressFinalize(this); + CopyFrom(connection); + _connectionString = connection._connectionString; + if (connection._credential != null) { - throw SQL.NullCustomKeyStoreProviderDictionary(); + SecureString password = connection._credential.Password.Copy(); + password.MakeReadOnly(); + _credential = new SqlCredential(connection._credential.UserId, password); } - // Validate that custom provider list doesn't contain any of system provider list - foreach (string key in customProviders.Keys) - { - // Validate the provider name - // Check for null or empty - if (string.IsNullOrWhiteSpace(key)) - { - throw SQL.EmptyProviderName(); - } - - // Check if the name starts with MSSQL_, since this is reserved namespace for system providers. - if (key.StartsWith(ADP.ColumnEncryptionSystemProviderNamePrefix, StringComparison.InvariantCultureIgnoreCase)) - { - throw SQL.InvalidCustomKeyStoreProviderName(key, ADP.ColumnEncryptionSystemProviderNamePrefix); - } - - // Validate the provider value - if (customProviders[key] is null) - { - throw SQL.NullProviderValue(key); - } - } + _accessToken = connection._accessToken; + _accessTokenCallback = connection._accessTokenCallback; + CacheConnectionStringProperties(); } internal static bool TryGetSystemColumnEncryptionKeyStoreProvider(string keyStoreName, out SqlColumnEncryptionKeyStoreProvider provider) @@ -282,150 +293,121 @@ internal List GetColumnEncryptionCustomKeyStoreProvidersNames() return new List(0); } - private bool _AsyncCommandInProgress; - - // SQLStatistics support - internal SqlStatistics _statistics; - private bool _collectstats; - - private bool _fireInfoMessageEventOnUserErrors; // False by default - - // root task associated with current async invocation - Tuple, Task> _currentCompletion; - - private SqlCredential _credential; // SQL authentication password stored in SecureString - private string _connectionString; - private int _connectRetryCount; - - private string _accessToken; // Access Token to be used for token based authententication - - // connection resiliency - private object _reconnectLock = new object(); - internal Task _currentReconnectionTask; - private Task _asyncWaitingForReconnection; // current async task waiting for reconnection in non-MARS connections - private Guid _originalConnectionId = Guid.Empty; - private CancellationTokenSource _reconnectionCancellationSource; - internal SessionData _recoverySessionData; - internal WindowsIdentity _lastIdentity; - internal WindowsIdentity _impersonateIdentity; - private int _reconnectCount; - - // Retry Logic - private SqlRetryLogicBaseProvider _retryLogicProvider; - private bool IsProviderRetriable => SqlConfigurableRetryFactory.IsRetriable(RetryLogicProvider); - - /// - [ - Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden) - ] - public SqlRetryLogicBaseProvider RetryLogicProvider + /// + /// Is this connection using column encryption ? + /// + internal bool IsColumnEncryptionSettingEnabled { get { - if (_retryLogicProvider == null) - { - _retryLogicProvider = SqlConfigurableRetryLogicManager.ConnectionProvider; - } - return _retryLogicProvider; - } - set - { - _retryLogicProvider = value; + SqlConnectionString opt = (SqlConnectionString)ConnectionOptions; + return opt?.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled; } } - // Transient Fault handling flag. This is needed to convey to the downstream mechanism of connection establishment, if Transient Fault handling should be used or not - // The downstream handling of Connection open is the same for idle connection resiliency. Currently we want to apply transient fault handling only to the connections opened - // using SqlConnection.Open() method. - internal bool _applyTransientFaultHandling = false; - - /// - public SqlConnection(string connectionString) : this(connectionString, null) + /// + public static void RegisterColumnEncryptionKeyStoreProviders(IDictionary customProviders) { - } + ValidateCustomProviders(customProviders); - /// - public SqlConnection(string connectionString, SqlCredential credential) : this() - { - ConnectionString = connectionString; // setting connection string first so that ConnectionOption is available - if (credential != null) + lock (s_globalCustomColumnEncryptionKeyProvidersLock) { - // The following checks are necessary as setting Credential property will call CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential - // CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential it will throw InvalidOperationException rather than Arguemtn exception - // Need to call setter on Credential property rather than setting _credential directly as pool groups need to be checked - SqlConnectionString connectionOptions = (SqlConnectionString)ConnectionOptions; - if (UsesClearUserIdOrPassword(connectionOptions)) + // Provider list can only be set once + if (s_globalCustomColumnEncryptionKeyStoreProviders is not null) { - throw ADP.InvalidMixedArgumentOfSecureAndClearCredential(); + throw SQL.CanOnlyCallOnce(); } - if (UsesIntegratedSecurity(connectionOptions)) + // AKV provider registration supports multi-user scenarios, so it is not safe to cache the CEK in the global provider. + // The CEK cache is a global cache, and is shared across all connections. + // To prevent conflicts between CEK caches, global providers should not use their own CEK caches + foreach (SqlColumnEncryptionKeyStoreProvider provider in customProviders.Values) { - throw ADP.InvalidMixedArgumentOfSecureCredentialAndIntegratedSecurity(); + provider.ColumnEncryptionKeyCacheTtl = new TimeSpan(0); } - if (UsesContextConnection(connectionOptions)) - { - throw ADP.InvalidMixedArgumentOfSecureCredentialAndContextConnection(); - } + // Create a temporary dictionary and then add items from the provided dictionary. + // Dictionary constructor does shallow copying by simply copying the provider name and provider reference pairs + // in the provided customerProviders dictionary. + Dictionary customColumnEncryptionKeyStoreProviders = + new(customProviders, StringComparer.OrdinalIgnoreCase); - if (UsesActiveDirectoryIntegrated(connectionOptions)) - { - throw SQL.SettingCredentialWithIntegratedArgument(); - } + // Set the dictionary to the ReadOnly dictionary. + s_globalCustomColumnEncryptionKeyStoreProviders = customColumnEncryptionKeyStoreProviders; + } + } - if (UsesActiveDirectoryInteractive(connectionOptions)) - { - throw SQL.SettingCredentialWithInteractiveArgument(); - } + /// + public void RegisterColumnEncryptionKeyStoreProvidersOnConnection(IDictionary customProviders) + { + ValidateCustomProviders(customProviders); - if (UsesActiveDirectoryDeviceCodeFlow(connectionOptions)) - { - throw SQL.SettingCredentialWithDeviceFlowArgument(); - } + // Create a temporary dictionary and then add items from the provided dictionary. + // Dictionary constructor does shallow copying by simply copying the provider name and provider reference pairs + // in the provided customerProviders dictionary. + Dictionary customColumnEncryptionKeyStoreProviders = + new(customProviders, StringComparer.OrdinalIgnoreCase); + + // Set the dictionary to the ReadOnly dictionary. + // This method can be called more than once. Re-registering a new collection will replace the + // old collection of providers. + _customColumnEncryptionKeyStoreProviders = customColumnEncryptionKeyStoreProviders; + } - if (UsesActiveDirectoryManagedIdentity(connectionOptions)) - { - throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringBuilderUtil.ActiveDirectoryManagedIdentityString); - } + private static void ValidateCustomProviders(IDictionary customProviders) + { + // Throw when the provided dictionary is null. + if (customProviders is null) + { + throw SQL.NullCustomKeyStoreProviderDictionary(); + } - if (UsesActiveDirectoryMSI(connectionOptions)) + // Validate that custom provider list doesn't contain any of system provider list + foreach (string key in customProviders.Keys) + { + // Validate the provider name + // Check for null or empty + if (string.IsNullOrWhiteSpace(key)) { - throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringBuilderUtil.ActiveDirectoryMSIString); + throw SQL.EmptyProviderName(); } - if (UsesActiveDirectoryDefault(connectionOptions)) + // Check if the name starts with MSSQL_, since this is reserved namespace for system providers. + if (key.StartsWith(ADP.ColumnEncryptionSystemProviderNamePrefix, StringComparison.InvariantCultureIgnoreCase)) { - throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringBuilderUtil.ActiveDirectoryDefaultString); + throw SQL.InvalidCustomKeyStoreProviderName(key, ADP.ColumnEncryptionSystemProviderNamePrefix); } - if (UsesActiveDirectoryWorkloadIdentity(connectionOptions)) + // Validate the provider value + if (customProviders[key] is null) { - throw SQL.SettingCredentialWithNonInteractiveArgument(DbConnectionStringBuilderUtil.ActiveDirectoryWorkloadIdentityString); + throw SQL.NullProviderValue(key); } - - Credential = credential; } - // else - // credential == null: we should not set "Credential" as this will do additional validation check and - // checking pool groups which is not necessary. All necessary operation is already done by calling "ConnectionString = connectionString" } - private SqlConnection(SqlConnection connection) - { // Clone - GC.SuppressFinalize(this); - CopyFrom(connection); - _connectionString = connection._connectionString; - if (connection._credential != null) - { - SecureString password = connection._credential.Password.Copy(); - password.MakeReadOnly(); - _credential = new SqlCredential(connection._credential.UserId, password); - } - _accessToken = connection._accessToken; - _accessTokenCallback = connection._accessTokenCallback; - CacheConnectionStringProperties(); + /// + /// Get enclave attestation url to be used with enclave based Always Encrypted + /// + internal string EnclaveAttestationUrl + { + get => ((SqlConnectionString)ConnectionOptions).EnclaveAttestationUrl; + } + + /// + /// Get attestation protocol + /// + internal SqlConnectionAttestationProtocol AttestationProtocol + { + get => ((SqlConnectionString)ConnectionOptions).AttestationProtocol; + } + + /// + /// Get IP address preference + /// + internal SqlConnectionIPAddressPreference iPAddressPreference + { + get => ((SqlConnectionString)ConnectionOptions).IPAddressPreference; } // This method will be called once connection string is set or changed. @@ -464,11 +446,9 @@ private void CacheConnectionStringProperties() // Create a new SqlStatistics object if not already there. // connect the parser to the object. // if there is no parser at this time we need to connect it after creation. - [ - DefaultValue(false), - ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data), - ResDescriptionAttribute(StringsHelper.ResourceNames.SqlConnection_StatisticsEnabled), - ] + [DefaultValue(false)] + [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data)] + [ResDescription(StringsHelper.ResourceNames.SqlConnection_StatisticsEnabled)] public bool StatisticsEnabled { get @@ -477,122 +457,47 @@ public bool StatisticsEnabled } set { - if (IsContextConnection) + if (value) { - if (value) + // start + if (ConnectionState.Open == State) { - throw SQL.NotAvailableOnContextConnection(); + if (_statistics == null) + { + _statistics = new SqlStatistics(); + _statistics._openTimestamp = ADP.TimerCurrent(); + } + // set statistics on the parser + // update timestamp; + Debug.Assert(Parser != null, "Where's the parser?"); + Parser.Statistics = _statistics; } } else { - if (value) + // stop + if (_statistics != null) { - // start if (ConnectionState.Open == State) { - if (_statistics == null) - { - _statistics = new SqlStatistics(); - _statistics._openTimestamp = ADP.TimerCurrent(); - } - // set statistics on the parser + // remove statistics from parser // update timestamp; - Debug.Assert(Parser != null, "Where's the parser?"); - Parser.Statistics = _statistics; - } - } - else - { - // stop - if (_statistics != null) - { - if (ConnectionState.Open == State) - { - // remove statistics from parser - // update timestamp; - TdsParser parser = Parser; - Debug.Assert(parser != null, "Where's the parser?"); - parser.Statistics = null; - _statistics._closeTimestamp = ADP.TimerCurrent(); - } + TdsParser parser = Parser; + Debug.Assert(parser != null, "Where's the parser?"); + parser.Statistics = null; + _statistics._closeTimestamp = ADP.TimerCurrent(); } } - this._collectstats = value; } + _collectstats = value; } } internal bool AsyncCommandInProgress { - get - { - return (_AsyncCommandInProgress); - } + get => _AsyncCommandInProgress; [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - set - { - _AsyncCommandInProgress = value; - } - } - - internal bool IsContextConnection - { - get - { - SqlConnectionString opt = (SqlConnectionString)ConnectionOptions; - return UsesContextConnection(opt); - } - } - - /// - /// Is this connection using column encryption ? - /// - internal bool IsColumnEncryptionSettingEnabled - { - get - { - SqlConnectionString opt = (SqlConnectionString)ConnectionOptions; - return opt != null ? opt.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled : false; - } - } - - /// - /// Get enclave attestation url to be used with enclave based Always Encrypted - /// - internal string EnclaveAttestationUrl - { - get - { - SqlConnectionString opt = (SqlConnectionString)ConnectionOptions; - return opt.EnclaveAttestationUrl; - } - } - - /// - /// Get attestation protocol - /// - internal SqlConnectionAttestationProtocol AttestationProtocol - { - get - { - SqlConnectionString opt = (SqlConnectionString)ConnectionOptions; - return opt.AttestationProtocol; - } - } - - /// - /// Get IP address preference - /// - internal SqlConnectionIPAddressPreference iPAddressPreference - { - get => ((SqlConnectionString)ConnectionOptions).IPAddressPreference; - } - - // Is this connection is a Context Connection? - private bool UsesContextConnection(SqlConnectionString opt) - { - return opt != null && opt.ContextConnection; + set => _AsyncCommandInProgress = value; } private bool UsesActiveDirectoryIntegrated(SqlConnectionString opt) @@ -635,163 +540,53 @@ private bool UsesAuthentication(SqlConnectionString opt) return opt != null && opt.Authentication != SqlAuthenticationMethod.NotSpecified; } - // Does this connection uses Integrated Security? + // Does this connection use Integrated Security? private bool UsesIntegratedSecurity(SqlConnectionString opt) { return opt != null && opt.IntegratedSecurity; } - // Does this connection uses old style of clear userID or Password in connection string? + // Does this connection use old style of clear userID or Password in connection string? private bool UsesClearUserIdOrPassword(SqlConnectionString opt) { bool result = false; if (opt != null) { - result = (!ADP.IsEmpty(opt.UserID) || !ADP.IsEmpty(opt.Password)); + result = (!string.IsNullOrEmpty(opt.UserID) || !string.IsNullOrEmpty(opt.Password)); } return result; } internal SqlConnectionString.TransactionBindingEnum TransactionBinding { - get - { - return ((SqlConnectionString)ConnectionOptions).TransactionBinding; - } + get => ((SqlConnectionString)ConnectionOptions).TransactionBinding; } internal SqlConnectionString.TypeSystem TypeSystem { - get - { - return ((SqlConnectionString)ConnectionOptions).TypeSystemVersion; - } + get => ((SqlConnectionString)ConnectionOptions).TypeSystemVersion; } internal Version TypeSystemAssemblyVersion { - get - { - return ((SqlConnectionString)ConnectionOptions).TypeSystemAssemblyVersion; - } - } - - internal PoolBlockingPeriod PoolBlockingPeriod - { - get - { - return ((SqlConnectionString)ConnectionOptions).PoolBlockingPeriod; - } + get => ((SqlConnectionString)ConnectionOptions).TypeSystemAssemblyVersion; } internal int ConnectRetryInterval { - get - { - return ((SqlConnectionString)ConnectionOptions).ConnectRetryInterval; - } - } - - /// - override protected DbProviderFactory DbProviderFactory - { - get - { - return SqlClientFactory.Instance; - } - } - - // AccessToken: To be used for token based authentication - /// - [ - Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), - ResDescriptionAttribute(StringsHelper.ResourceNames.SqlConnection_AccessToken), - ] - public string AccessToken - { - get - { - string result = _accessToken; - // When a connection is connecting or is ever opened, make AccessToken available only if "Persist Security Info" is set to true - // otherwise, return null - SqlConnectionString connectionOptions = (SqlConnectionString)UserConnectionOptions; - if (InnerConnection.ShouldHidePassword && connectionOptions != null && !connectionOptions.PersistSecurityInfo) - { - result = null; - } - - return result; - } - set - { - // If a connection is connecting or is ever opened, AccessToken cannot be set - if (!InnerConnection.AllowSetConnectionString) - { - throw ADP.OpenConnectionPropertySet("AccessToken", InnerConnection.State); - } - - if (value != null) - { - // Check if the usage of AccessToken has any conflict with the keys used in connection string and credential - CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken((SqlConnectionString)ConnectionOptions); - } - - _accessToken = value; - // Need to call ConnectionString_Set to do proper pool group check - ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential, _accessToken, null)); - } - } - - /// - public Func> AccessTokenCallback - { - get { return _accessTokenCallback; } - set - { - // If a connection is connecting or is ever opened, AccessToken callback cannot be set - if (!InnerConnection.AllowSetConnectionString) - { - throw ADP.OpenConnectionPropertySet(nameof(AccessTokenCallback), InnerConnection.State); - } - - if (value != null) - { - // Check if the usage of AccessToken has any conflict with the keys used in connection string and credential - CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessTokenCallback((SqlConnectionString)ConnectionOptions); - } - - ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential, null, value)); - _accessTokenCallback = value; - } - } - - /// - [ - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), - ResDescriptionAttribute(StringsHelper.ResourceNames.SqlConnection_ConnectionTimeout), - ] - public int CommandTimeout - { - get - { - SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; - return constr != null ? constr.CommandTimeout : SqlConnectionString.DEFAULT.Command_Timeout; - } + get => ((SqlConnectionString)ConnectionOptions).ConnectRetryInterval; } /// - [ - DefaultValue(""), + [DefaultValue("")] #pragma warning disable 618 // ignore obsolete warning about RecommendedAsConfigurable to use SettingsBindableAttribute - RecommendedAsConfigurable(true), + [RecommendedAsConfigurable(true)] #pragma warning restore 618 - SettingsBindableAttribute(true), - RefreshProperties(RefreshProperties.All), - ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data), - ResDescriptionAttribute(StringsHelper.ResourceNames.SqlConnection_ConnectionString), - ] - override public string ConnectionString + [SettingsBindableAttribute(true)] + [RefreshProperties(RefreshProperties.All)] + [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data)] + [ResDescription(StringsHelper.ResourceNames.SqlConnection_ConnectionString)] + public override string ConnectionString { get { @@ -805,7 +600,7 @@ override public string ConnectionString if (_credential != null) { // Check for Credential being used with Authentication=ActiveDirectoryIntegrated | ActiveDirectoryInteractive | - // ActiveDirectoryDeviceCodeFlow | ActiveDirectoryManagedIdentity/ActiveDirectoryMSI | ActiveDirectoryDefault. Since a different error string is used + // ActiveDirectoryDeviceCodeFlow | ActiveDirectoryManagedIdentity/ActiveDirectoryMSI | ActiveDirectoryDefault. Since a different error string is used // for this case in ConnectionString setter vs in Credential setter, check for this error case before calling // CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential, which is common to both setters. if (UsesActiveDirectoryIntegrated(connectionOptions)) @@ -822,60 +617,125 @@ override public string ConnectionString } else if (UsesActiveDirectoryManagedIdentity(connectionOptions)) { - throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringBuilderUtil.ActiveDirectoryManagedIdentityString); + throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringUtilities.ActiveDirectoryManagedIdentityString); } else if (UsesActiveDirectoryMSI(connectionOptions)) { - throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringBuilderUtil.ActiveDirectoryMSIString); + throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringUtilities.ActiveDirectoryMSIString); } else if (UsesActiveDirectoryDefault(connectionOptions)) { - throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringBuilderUtil.ActiveDirectoryDefaultString); + throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringUtilities.ActiveDirectoryDefaultString); } else if (UsesActiveDirectoryWorkloadIdentity(connectionOptions)) { - throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringBuilderUtil.ActiveDirectoryWorkloadIdentityString); + throw SQL.SettingNonInteractiveWithCredential(DbConnectionStringUtilities.ActiveDirectoryWorkloadIdentityString); } CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); } - if (_accessToken != null) - { - CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken(connectionOptions); - } + if (_accessToken != null) + { + CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken(connectionOptions); + } + + if (_accessTokenCallback != null) + { + CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessTokenCallback(connectionOptions); + } + } + ConnectionString_Set(new SqlConnectionPoolKey(value, _credential, _accessToken, _accessTokenCallback)); + _connectionString = value; // Change _connectionString value only after value is validated + CacheConnectionStringProperties(); + } + } + + /// + [ResDescription(StringsHelper.ResourceNames.SqlConnection_ConnectionTimeout)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override int ConnectionTimeout + { + get + { + SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; + return constr != null ? constr.ConnectTimeout : SqlConnectionString.DEFAULT.Connect_Timeout; + } + } + + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [ResDescription(StringsHelper.ResourceNames.SqlConnection_ConnectionTimeout)] + public int CommandTimeout + { + get + { + SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; + return constr != null ? constr.CommandTimeout : SqlConnectionString.DEFAULT.Command_Timeout; + } + } + + /// + // AccessToken: To be used for token based authentication + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [ResDescription(StringsHelper.ResourceNames.SqlConnection_AccessToken)] + public string AccessToken + { + get + { + // When a connection is connecting or is ever opened, make AccessToken available only if "Persist Security Info" is set to true + // otherwise, return null + SqlConnectionString connectionOptions = (SqlConnectionString)UserConnectionOptions; + return InnerConnection.ShouldHidePassword && connectionOptions != null && !connectionOptions.PersistSecurityInfo ? null : _accessToken; + } + set + { + // If a connection is connecting or is ever opened, AccessToken cannot be set + if (!InnerConnection.AllowSetConnectionString) + { + throw ADP.OpenConnectionPropertySet("AccessToken", InnerConnection.State); + } - if (_accessTokenCallback != null) - { - CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessTokenCallback(connectionOptions); - } + if (value != null) + { + // Check if the usage of AccessToken has any conflict with the keys used in connection string and credential + CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken((SqlConnectionString)ConnectionOptions); } - ConnectionString_Set(new SqlConnectionPoolKey(value, _credential, _accessToken, _accessTokenCallback)); - _connectionString = value; // Change _connectionString value only after value is validated - CacheConnectionStringProperties(); + + _accessToken = value; + // Need to call ConnectionString_Set to do proper pool group check + ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential, _accessToken, null)); } } - /// - [ - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), - ResDescriptionAttribute(StringsHelper.ResourceNames.SqlConnection_ConnectionTimeout), - ] - override public int ConnectionTimeout + /// + public Func> AccessTokenCallback { - get + get { return _accessTokenCallback; } + set { - SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; - return constr != null ? constr.ConnectTimeout : SqlConnectionString.DEFAULT.Connect_Timeout; + // If a connection is connecting or is ever opened, AccessToken callback cannot be set + if (!InnerConnection.AllowSetConnectionString) + { + throw ADP.OpenConnectionPropertySet(nameof(AccessTokenCallback), InnerConnection.State); + } + + if (value != null) + { + // Check if the usage of AccessToken has any conflict with the keys used in connection string and credential + CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessTokenCallback((SqlConnectionString)ConnectionOptions); + } + + ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential, null, value)); + _accessTokenCallback = value; } } /// - [ - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), - ResDescriptionAttribute(StringsHelper.ResourceNames.SqlConnection_Database), - ] - override public string Database + [ResDescription(StringsHelper.ResourceNames.SqlConnection_Database)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override string Database { // if the connection is open, we need to ask the inner connection what it's // current catalog is because it may have gotten changed, otherwise we can @@ -947,12 +807,10 @@ internal string SQLDNSCachingSupportedStateBeforeRedirect } /// - [ - Browsable(true), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), - ResDescriptionAttribute(StringsHelper.ResourceNames.SqlConnection_DataSource), - ] - override public string DataSource + [Browsable(true)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [ResDescription(StringsHelper.ResourceNames.SqlConnection_DataSource)] + public override string DataSource { get { @@ -973,11 +831,9 @@ override public string DataSource } /// - [ - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), - ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data), - ResDescriptionAttribute(StringsHelper.ResourceNames.SqlConnection_PacketSize), - ] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] + [ResDescription(StringsHelper.ResourceNames.SqlConnection_PacketSize)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public int PacketSize { // if the connection is open, we need to ask the inner connection what it's @@ -985,15 +841,8 @@ public int PacketSize // can just return what the connection string had. get { - if (IsContextConnection) - { - throw SQL.NotAvailableOnContextConnection(); - } - - SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds); int result; - - if (innerConnection != null) + if (InnerConnection is SqlInternalConnectionTds innerConnection) { result = innerConnection.PacketSize; } @@ -1002,21 +851,19 @@ public int PacketSize SqlConnectionString constr = (SqlConnectionString)ConnectionOptions; result = constr != null ? constr.PacketSize : SqlConnectionString.DEFAULT.Packet_Size; } + return result; } } /// - [ - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), - ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data), - ResDescriptionAttribute(StringsHelper.ResourceNames.SqlConnection_ClientConnectionId), - ] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] + [ResDescription(StringsHelper.ResourceNames.SqlConnection_ClientConnectionId)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public Guid ClientConnectionId { get { - SqlInternalConnectionTds innerConnection = (InnerConnection as SqlInternalConnectionTds); if (innerConnection != null) @@ -1038,12 +885,10 @@ public Guid ClientConnectionId } /// - [ - Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), - ResDescriptionAttribute(StringsHelper.ResourceNames.SqlConnection_ServerVersion), - ] - override public string ServerVersion + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [ResDescription(StringsHelper.ResourceNames.SqlConnection_ServerVersion)] + public override string ServerVersion { get { @@ -1052,11 +897,9 @@ override public string ServerVersion } /// - [ - Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), - ResDescription(StringsHelper.ResourceNames.SqlConnection_ServerProcessId), - ] + [Browsable(false)] + [ResDescription(StringsHelper.ResourceNames.SqlConnection_ServerProcessId)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public int ServerProcessId { get => State.Equals(ConnectionState.Open) | State.Equals(ConnectionState.Executing) | State.Equals(ConnectionState.Fetching) ? @@ -1064,12 +907,10 @@ public int ServerProcessId } /// - [ - Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), - ResDescriptionAttribute(StringsHelper.ResourceNames.DbConnection_State), - ] - override public ConnectionState State + [Browsable(false)] + [ResDescription(StringsHelper.ResourceNames.DbConnection_State)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public override ConnectionState State { get { @@ -1085,27 +926,17 @@ override public ConnectionState State internal SqlStatistics Statistics { - get - { - return _statistics; - } + get => _statistics; } /// - [ - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), - ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_Data), - ResDescriptionAttribute(StringsHelper.ResourceNames.SqlConnection_WorkstationId), - ] + [ResCategory(StringsHelper.ResourceNames.DataCategory_Data)] + [ResDescription(StringsHelper.ResourceNames.SqlConnection_WorkstationId)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public string WorkstationId { get { - if (IsContextConnection) - { - throw SQL.NotAvailableOnContextConnection(); - } - // If not supplied by the user, the default value is the MachineName // Note: In Longhorn you'll be able to rename a machine without // rebooting. Therefore, don't cache this machine name. @@ -1122,12 +953,9 @@ public string WorkstationId } /// - // SqlCredential: Pair User Id and password in SecureString which are to be used for SQL authentication - [ - Browsable(false), - DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), - ResDescriptionAttribute(StringsHelper.ResourceNames.SqlConnection_Credential), - ] + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [ResDescription(StringsHelper.ResourceNames.SqlConnection_Credential)] public SqlCredential Credential { get @@ -1150,7 +978,7 @@ public SqlCredential Credential // If a connection is connecting or is ever opened, user id/password cannot be set if (!InnerConnection.AllowSetConnectionString) { - throw ADP.OpenConnectionPropertySet("Credential", InnerConnection.State); + throw ADP.OpenConnectionPropertySet(nameof(Credential), InnerConnection.State); } // check if the usage of credential has any conflict with the keys used in connection string @@ -1175,19 +1003,19 @@ public SqlCredential Credential } else if (UsesActiveDirectoryManagedIdentity(connectionOptions)) { - throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringBuilderUtil.ActiveDirectoryManagedIdentityString); + throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringUtilities.ActiveDirectoryManagedIdentityString); } else if (UsesActiveDirectoryMSI(connectionOptions)) { - throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringBuilderUtil.ActiveDirectoryMSIString); + throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringUtilities.ActiveDirectoryMSIString); } else if (UsesActiveDirectoryDefault(connectionOptions)) { - throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringBuilderUtil.ActiveDirectoryDefaultString); + throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringUtilities.ActiveDirectoryDefaultString); } else if (UsesActiveDirectoryWorkloadIdentity(connectionOptions)) { - throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringBuilderUtil.ActiveDirectoryWorkloadIdentityString); + throw SQL.SettingCredentialWithNonInteractiveInvalid(DbConnectionStringUtilities.ActiveDirectoryWorkloadIdentityString); } CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(connectionOptions); @@ -1201,14 +1029,14 @@ public SqlCredential Credential _credential = value; // Need to call ConnectionString_Set to do proper pool group check - ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential, _accessToken, _accessTokenCallback)); + ConnectionString_Set(new SqlConnectionPoolKey(_connectionString, _credential, accessToken: _accessToken, accessTokenCallback: _accessTokenCallback)); } } // CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential: check if the usage of credential has any conflict // with the keys used in connection string // If there is any conflict, it throws InvalidOperationException - // This is to be used setter of ConnectionString and Credential properties + // This is used in the setter of ConnectionString and Credential properties. private void CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential(SqlConnectionString connectionOptions) { if (UsesClearUserIdOrPassword(connectionOptions)) @@ -1220,11 +1048,6 @@ private void CheckAndThrowOnInvalidCombinationOfConnectionStringAndSqlCredential { throw ADP.InvalidMixedUsageOfSecureCredentialAndIntegratedSecurity(); } - - if (UsesContextConnection(connectionOptions)) - { - throw ADP.InvalidMixedArgumentOfSecureCredentialAndContextConnection(); - } } // CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken: check if the usage of AccessToken has any conflict @@ -1243,11 +1066,6 @@ private void CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessToken(S throw ADP.InvalidMixedUsageOfAccessTokenAndIntegratedSecurity(); } - if (UsesContextConnection(connectionOptions)) - { - throw ADP.InvalidMixedUsageOfAccessTokenAndContextConnection(); - } - if (UsesAuthentication(connectionOptions)) { throw ADP.InvalidMixedUsageOfAccessTokenAndAuthentication(); @@ -1287,15 +1105,19 @@ private void CheckAndThrowOnInvalidCombinationOfConnectionOptionAndAccessTokenCa } } + /// + protected override DbProviderFactory DbProviderFactory + { + get => SqlClientFactory.Instance; + } + // // PUBLIC EVENTS // /// - [ - ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_InfoMessage), - ResDescriptionAttribute(StringsHelper.ResourceNames.DbConnection_InfoMessage), - ] + [ResCategoryAttribute(StringsHelper.ResourceNames.DataCategory_InfoMessage)] + [ResDescription(StringsHelper.ResourceNames.DbConnection_InfoMessage)] public event SqlInfoMessageEventHandler InfoMessage { add @@ -1311,25 +1133,18 @@ public event SqlInfoMessageEventHandler InfoMessage /// public bool FireInfoMessageEventOnUserErrors { - get - { - return _fireInfoMessageEventOnUserErrors; - } - set - { - _fireInfoMessageEventOnUserErrors = value; - } + get => _fireInfoMessageEventOnUserErrors; + set => _fireInfoMessageEventOnUserErrors = value; } // Approx. number of times that the internal connection has been reconnected internal int ReconnectCount { - get - { - return _reconnectCount; - } + get => _reconnectCount; } + internal bool ForceNewConnection { get; set; } + // // PUBLIC METHODS // @@ -1388,9 +1203,6 @@ public SqlTransaction BeginTransaction(IsolationLevel iso, string transactionNam { statistics = SqlStatistics.StartTimer(Statistics); - // NOTE: we used to throw an exception if the transaction name was empty - // (see MDAC 50292) but that was incorrect because we have a BeginTransaction - // method that doesn't have a transactionName argument. SqlTransaction transaction; bool isFirstAttempt = true; do @@ -1401,7 +1213,7 @@ public SqlTransaction BeginTransaction(IsolationLevel iso, string transactionNam } while (transaction.InternalTransaction.ConnectionHasBeenRestored); - // SQLBU 503873 The GetOpenConnection line above doesn't keep a ref on the outer connection (this), + // The GetOpenConnection line above doesn't keep a ref on the outer connection (this), // and it could be collected before the inner connection can hook it to the transaction, resulting in // a transaction with a null connection property. Use GC.KeepAlive to ensure this doesn't happen. GC.KeepAlive(this); @@ -1416,7 +1228,7 @@ public SqlTransaction BeginTransaction(IsolationLevel iso, string transactionNam } /// - override public void ChangeDatabase(string database) + public override void ChangeDatabase(string database) { SqlStatistics statistics = null; RepairInnerConnection(); @@ -1426,26 +1238,9 @@ override public void ChangeDatabase(string database) try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(this); - statistics = SqlStatistics.StartTimer(Statistics); - InnerConnection.ChangeDatabase(database); - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(this); + statistics = SqlStatistics.StartTimer(Statistics); + InnerConnection.ChangeDatabase(database); } catch (System.OutOfMemoryException e) { @@ -1470,116 +1265,88 @@ override public void ChangeDatabase(string database) } /// - static public void ClearAllPools() + public static void ClearAllPools() { (new SqlClientPermission(PermissionState.Unrestricted)).Demand(); SqlConnectionFactory.SingletonInstance.ClearAllPools(); } /// - static public void ClearPool(SqlConnection connection) + public static void ClearPool(SqlConnection connection) { - ADP.CheckArgumentNull(connection, "connection"); + ADP.CheckArgumentNull(connection, nameof(connection)); DbConnectionOptions connectionOptions = connection.UserConnectionOptions; if (connectionOptions != null) { connectionOptions.DemandPermission(); - if (connection.IsContextConnection) - { - throw SQL.NotAvailableOnContextConnection(); - } SqlConnectionFactory.SingletonInstance.ClearPool(connection); } } - /// - object ICloneable.Clone() - { - SqlConnection clone = new SqlConnection(this); - return clone; - } - - void CloseInnerConnection() + private void CloseInnerConnection() { // CloseConnection() now handles the lock // The SqlInternalConnectionTds is set to OpenBusy during close, once this happens the cast below will fail and - // the command will no longer be cancelable. It might be desirable to be able to cancel the close opperation, but this is + // the command will no longer be cancelable. It might be desirable to be able to cancel the close operation, but this is // outside of the scope of Whidbey RTM. See (SqlCommand::Cancel) for other lock. _originalConnectionId = ClientConnectionId; InnerConnection.CloseConnection(this, ConnectionFactory); } /// - override public void Close() + public override void Close() { using (TryEventScope.Create(" {0}", ObjectID)) { SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current); SqlStatistics statistics = null; - TdsParser bestEffortCleanupTarget = null; + RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(this); + statistics = SqlStatistics.StartTimer(Statistics); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else + Task reconnectTask = _currentReconnectionTask; + if (reconnectTask != null && !reconnectTask.IsCompleted) { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(this); - statistics = SqlStatistics.StartTimer(Statistics); - - Task reconnectTask = _currentReconnectionTask; - if (reconnectTask != null && !reconnectTask.IsCompleted) + CancellationTokenSource cts = _reconnectionCancellationSource; + if (cts != null) { - CancellationTokenSource cts = _reconnectionCancellationSource; - if (cts != null) - { - cts.Cancel(); - } - AsyncHelper.WaitForCompletion(reconnectTask, 0, null, rethrowExceptions: false); // we do not need to deal with possible exceptions in reconnection - if (State != ConnectionState.Open) - {// if we cancelled before the connection was opened - OnStateChange(DbConnectionInternal.StateChangeClosed); - } + cts.Cancel(); } - CancelOpenAndWait(); - CloseInnerConnection(); - GC.SuppressFinalize(this); - - if (Statistics != null) - { - _statistics._closeTimestamp = ADP.TimerCurrent(); + AsyncHelper.WaitForCompletion(reconnectTask, 0, null, rethrowExceptions: false); // we do not need to deal with possible exceptions in reconnection + if (State != ConnectionState.Open) + {// if we cancelled before the connection was opened + OnStateChange(DbConnectionInternal.StateChangeClosed); } } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG + CancelOpenAndWait(); + CloseInnerConnection(); + GC.SuppressFinalize(this); + + if (Statistics != null) + { + _statistics._closeTimestamp = ADP.TimerCurrent(); + } } - catch (System.OutOfMemoryException e) + catch (System.OutOfMemoryException ex) { - Abort(e); + Abort(ex); throw; } - catch (System.StackOverflowException e) + catch (System.StackOverflowException ex) { - Abort(e); + Abort(ex); throw; } - catch (System.Threading.ThreadAbortException e) + catch (System.Threading.ThreadAbortException ex) { - Abort(e); + Abort(ex); SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); throw; } @@ -1627,21 +1394,12 @@ private void DisposeMe(bool disposing) } /// - public void EnlistDistributedTransaction(System.EnterpriseServices.ITransaction transaction) - { - if (IsContextConnection) - { - throw SQL.NotAvailableOnContextConnection(); - } - + public void EnlistDistributedTransaction(System.EnterpriseServices.ITransaction transaction) => EnlistDistributedTransactionHelper(transaction); - } /// - override public void Open() - { + public override void Open() => Open(SqlConnectionOverrides.None); - } private bool TryOpenWithRetry(TaskCompletionSource retry, SqlConnectionOverrides overrides) => RetryLogicProvider.Execute(this, () => TryOpen(retry, overrides)); @@ -1869,7 +1627,7 @@ internal Task ValidateAndReconnect(Action beforeDisconnect, int timeout) return runningReconnect; } - // this is straightforward, but expensive method to do connection resiliency - it take locks and all prepartions as for TDS request + // this is straightforward, but expensive method to do connection resiliency - it take locks and all preparations as for TDS request partial void RepairInnerConnection() { WaitForPendingReconnection(); @@ -1894,7 +1652,7 @@ private void WaitForPendingReconnection() } } - void CancelOpenAndWait() + private void CancelOpenAndWait() { // copy from member to avoid changes by background thread var completion = _currentCompletion; @@ -1954,13 +1712,6 @@ private Task InternalOpenAsync(SqlConnectionOverrides overrides, CancellationTok return result.Task; } - if (IsContextConnection) - { - // Async not supported on Context Connections - result.SetException(ADP.ExceptionWithStackTrace(SQL.NotAvailableOnContextConnection())); - return result.Task; - } - bool completed; try @@ -2005,11 +1756,11 @@ private Task InternalOpenAsync(SqlConnectionOverrides overrides, CancellationTok private class OpenAsyncRetry { - SqlConnection _parent; - TaskCompletionSource _retry; - TaskCompletionSource _result; - SqlConnectionOverrides _overrides; - CancellationTokenRegistration _registration; + private SqlConnection _parent; + private TaskCompletionSource _retry; + private TaskCompletionSource _result; + private SqlConnectionOverrides _overrides; + private CancellationTokenRegistration _registration; public OpenAsyncRetry(SqlConnection parent, TaskCompletionSource retry, TaskCompletionSource result, SqlConnectionOverrides overrides, CancellationTokenRegistration registration) { @@ -2138,69 +1889,44 @@ private bool TryOpenInner(TaskCompletionSource retry) RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try + if (ForceNewConnection) { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - if (ForceNewConnection) + if (!InnerConnection.TryReplaceConnection(this, ConnectionFactory, retry, UserConnectionOptions)) { - if (!InnerConnection.TryReplaceConnection(this, ConnectionFactory, retry, UserConnectionOptions)) - { - return false; - } + return false; } - else + } + else + { + if (!InnerConnection.TryOpenConnection(this, ConnectionFactory, retry, UserConnectionOptions)) { - if (!InnerConnection.TryOpenConnection(this, ConnectionFactory, retry, UserConnectionOptions)) - { - return false; - } + return false; } - // does not require GC.KeepAlive(this) because of OnStateChange + } + // does not require GC.KeepAlive(this) because of OnStateChange - // GetBestEffortCleanup must happen AFTER OpenConnection to get the correct target. - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(this); + // GetBestEffortCleanup must happen AFTER OpenConnection to get the correct target. + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(this); - var tdsInnerConnection = (InnerConnection as SqlInternalConnectionTds); - if (tdsInnerConnection == null) - { - SqlInternalConnectionSmi innerConnection = (InnerConnection as SqlInternalConnectionSmi); - innerConnection.AutomaticEnlistment(); - } - else - { - Debug.Assert(tdsInnerConnection.Parser != null, "Where's the parser?"); + var tdsInnerConnection = (SqlInternalConnectionTds)InnerConnection; + Debug.Assert(tdsInnerConnection.Parser != null, "Where's the parser?"); - if (!tdsInnerConnection.ConnectionOptions.Pooling) - { - // For non-pooled connections, we need to make sure that the finalizer does actually run to avoid leaking SNI handles - GC.ReRegisterForFinalize(this); - } + if (!tdsInnerConnection.ConnectionOptions.Pooling) + { + // For non-pooled connections, we need to make sure that the finalizer does actually run to avoid leaking SNI handles + GC.ReRegisterForFinalize(this); + } - if (StatisticsEnabled) - { - _statistics._openTimestamp = ADP.TimerCurrent(); - tdsInnerConnection.Parser.Statistics = _statistics; - } - else - { - tdsInnerConnection.Parser.Statistics = null; - _statistics = null; // in case of previous Open/Close/reset_CollectStats sequence - } - } + if (StatisticsEnabled) + { + _statistics._openTimestamp = ADP.TimerCurrent(); + tdsInnerConnection.Parser.Statistics = _statistics; } -#if DEBUG - finally + else { - tdsReliabilitySection.Stop(); + tdsInnerConnection.Parser.Statistics = null; + _statistics = null; // in case of previous Open/Close/reset_CollectStats sequence } -#endif //DEBUG } catch (System.OutOfMemoryException e) { @@ -2248,30 +1974,6 @@ internal bool HasLocalTransactionFromAPI } } - internal bool Is2000 - { - get - { - if (_currentReconnectionTask != null) - { // holds true even if task is completed - return true; // if CR is enabled, connection, if established, will be 2008+ - } - return GetOpenConnection().Is2000; - } - } - - internal bool Is2005OrNewer - { - get - { - if (_currentReconnectionTask != null) - { // holds true even if task is completed - return true; // if CR is enabled, connection, if established, will be 2008+ - } - return GetOpenConnection().Is2005OrNewer; - } - } - internal bool Is2008OrNewer { get @@ -2330,9 +2032,9 @@ internal void ValidateConnectionForExecute(string method, SqlCommand command) // Surround name in brackets and then escape any end bracket to protect against SQL Injection. // NOTE: if the user escapes it themselves it will not work, but this was the case in V1 as well // as native OleDb and Odbc. - static internal string FixupDatabaseTransactionName(string name) + internal static string FixupDatabaseTransactionName(string name) { - if (!ADP.IsEmpty(name)) + if (!string.IsNullOrEmpty(name)) { return SqlServerEscapeHelper.EscapeIdentifier(name); } @@ -2348,16 +2050,8 @@ internal void OnError(SqlException exception, bool breakConnection, Action ActivityID {0}", ActivityCorrelator.Current); - if (ADP.IsEmpty(connectionString)) + if (string.IsNullOrEmpty(connectionString)) { throw SQL.ChangePasswordArgumentMissing("connectionString"); } - if (ADP.IsEmpty(newPassword)) + if (string.IsNullOrEmpty(newPassword)) { - throw SQL.ChangePasswordArgumentMissing("newPassword"); + throw SQL.ChangePasswordArgumentMissing(nameof(newPassword)); } if (TdsEnums.MAXLEN_NEWPASSWORD < newPassword.Length) { - throw ADP.InvalidArgumentLength("newPassword", TdsEnums.MAXLEN_NEWPASSWORD); + throw ADP.InvalidArgumentLength(nameof(newPassword), TdsEnums.MAXLEN_NEWPASSWORD); } SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential: null, accessToken: null, accessTokenCallback: null); @@ -2501,16 +2195,12 @@ public static void ChangePassword(string connectionString, string newPassword) { throw SQL.ChangePasswordConflictsWithSSPI(); } - if (!ADP.IsEmpty(connectionOptions.AttachDBFilename)) + if (!string.IsNullOrEmpty(connectionOptions.AttachDBFilename)) { throw SQL.ChangePasswordUseOfUnallowedKey(SqlConnectionString.KEY.AttachDBFilename); } - if (connectionOptions.ContextConnection) - { - throw SQL.ChangePasswordUseOfUnallowedKey(SqlConnectionString.KEY.Context_Connection); - } - System.Security.PermissionSet permissionSet = connectionOptions.CreatePermissionSet(); + PermissionSet permissionSet = connectionOptions.CreatePermissionSet(); permissionSet.Demand(); ChangePassword(connectionString, connectionOptions, null, newPassword, null); @@ -2524,38 +2214,38 @@ public static void ChangePassword(string connectionString, SqlCredential credent { SqlClientEventSource.Log.TryCorrelationTraceEvent(" ActivityID {0}", ActivityCorrelator.Current); - if (ADP.IsEmpty(connectionString)) + if (string.IsNullOrEmpty(connectionString)) { - throw SQL.ChangePasswordArgumentMissing("connectionString"); + throw SQL.ChangePasswordArgumentMissing(nameof(connectionString)); } // check credential; not necessary to check the length of password in credential as the check is done by SqlCredential class if (credential == null) { - throw SQL.ChangePasswordArgumentMissing("credential"); + throw SQL.ChangePasswordArgumentMissing(nameof(credential)); } if (newSecurePassword == null || newSecurePassword.Length == 0) { - throw SQL.ChangePasswordArgumentMissing("newSecurePassword"); + throw SQL.ChangePasswordArgumentMissing(nameof(newSecurePassword)); } if (!newSecurePassword.IsReadOnly()) { - throw ADP.MustBeReadOnly("newSecurePassword"); + throw ADP.MustBeReadOnly(nameof(newSecurePassword)); } if (TdsEnums.MAXLEN_NEWPASSWORD < newSecurePassword.Length) { - throw ADP.InvalidArgumentLength("newSecurePassword", TdsEnums.MAXLEN_NEWPASSWORD); + throw ADP.InvalidArgumentLength(nameof(newSecurePassword), TdsEnums.MAXLEN_NEWPASSWORD); } SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential, accessToken: null, accessTokenCallback: null); SqlConnectionString connectionOptions = SqlConnectionFactory.FindSqlConnectionOptions(key); - // Check for incompatible connection string value with SqlCredential - if (!ADP.IsEmpty(connectionOptions.UserID) || !ADP.IsEmpty(connectionOptions.Password)) + // Check for connection string values incompatible with SqlCredential + if (!string.IsNullOrEmpty(connectionOptions.UserID) || !string.IsNullOrEmpty(connectionOptions.Password)) { throw ADP.InvalidMixedArgumentOfSecureAndClearCredential(); } @@ -2565,17 +2255,12 @@ public static void ChangePassword(string connectionString, SqlCredential credent throw SQL.ChangePasswordConflictsWithSSPI(); } - if (!ADP.IsEmpty(connectionOptions.AttachDBFilename)) + if (!string.IsNullOrEmpty(connectionOptions.AttachDBFilename)) { throw SQL.ChangePasswordUseOfUnallowedKey(SqlConnectionString.KEY.AttachDBFilename); } - if (connectionOptions.ContextConnection) - { - throw SQL.ChangePasswordUseOfUnallowedKey(SqlConnectionString.KEY.Context_Connection); - } - - System.Security.PermissionSet permissionSet = connectionOptions.CreatePermissionSet(); + PermissionSet permissionSet = connectionOptions.CreatePermissionSet(); permissionSet.Demand(); ChangePassword(connectionString, connectionOptions, credential, null, newSecurePassword); @@ -2584,16 +2269,17 @@ public static void ChangePassword(string connectionString, SqlCredential credent private static void ChangePassword(string connectionString, SqlConnectionString connectionOptions, SqlCredential credential, string newPassword, SecureString newSecurePassword) { - // note: This is the only case where we directly construt the internal connection, passing in the new password. - // Normally we would simply create a regular connectoin and open it but there is no other way to pass the - // new password down to the constructor. Also it would have an unwanted impact on the connection pool - // - using (SqlInternalConnectionTds con = new SqlInternalConnectionTds(null, connectionOptions, credential, null, newPassword, newSecurePassword, false, null, null, null, null)) + // note: This is the only case where we directly construct the internal connection, passing in the new password. + // Normally we would simply create a regular connection and open it, but there is no other way to pass the + // new password down to the constructor. This would have an unwanted impact on the connection pool. + SqlInternalConnectionTds con = null; + try { - if (!con.Is2005OrNewer) - { - throw SQL.ChangePasswordRequires2005(); - } + con = new SqlInternalConnectionTds(null, connectionOptions, credential, null, newPassword, newSecurePassword, false); + } + finally + { + con?.Dispose(); } SqlConnectionPoolKey key = new SqlConnectionPoolKey(connectionString, credential, accessToken: null, accessTokenCallback: null); @@ -2648,11 +2334,6 @@ internal Task RegisterForConnectionCloseNotification(Task outerTask, ob /// public void ResetStatistics() { - if (IsContextConnection) - { - throw SQL.NotAvailableOnContextConnection(); - } - if (Statistics != null) { Statistics.Reset(); @@ -2667,11 +2348,6 @@ public void ResetStatistics() /// public IDictionary RetrieveStatistics() { - if (IsContextConnection) - { - throw SQL.NotAvailableOnContextConnection(); - } - if (Statistics != null) { UpdateStatistics(); @@ -2705,14 +2381,14 @@ public IDictionary RetrieveInternalInfo() return internalDictionary; } - // - // UDT SUPPORT - // + /// + object ICloneable.Clone() => new SqlConnection(this); + // UDT SUPPORT private Assembly ResolveTypeAssembly(AssemblyName asmRef, bool throwOnError) { Debug.Assert(TypeSystemAssemblyVersion != null, "TypeSystemAssembly should be set !"); - if (string.Compare(asmRef.Name, "Microsoft.SqlServer.Types", StringComparison.OrdinalIgnoreCase) == 0) + if (string.Equals(asmRef.Name, "Microsoft.SqlServer.Types", StringComparison.OrdinalIgnoreCase)) { if (asmRef.Version != TypeSystemAssemblyVersion && SqlClientEventSource.Log.IsTraceEnabled()) { @@ -2738,19 +2414,17 @@ private Assembly ResolveTypeAssembly(AssemblyName asmRef, bool throwOnError) } } - // TODO - move UDT code to separate file. internal void CheckGetExtendedUDTInfo(SqlMetaDataPriv metaData, bool fThrow) { if (metaData.udt?.Type == null) { // If null, we have not obtained extended info. - Debug.Assert(!ADP.IsEmpty(metaData.udt?.AssemblyQualifiedName), "Unexpected state on GetUDTInfo"); + Debug.Assert(!string.IsNullOrEmpty(metaData.udt?.AssemblyQualifiedName), "Unexpected state on GetUDTInfo"); // Parameter throwOnError determines whether exception from Assembly.Load is thrown. metaData.udt.Type = Type.GetType(typeName: metaData.udt.AssemblyQualifiedName, assemblyResolver: asmRef => ResolveTypeAssembly(asmRef, fThrow), typeResolver: null, throwOnError: fThrow); if (fThrow && metaData.udt.Type == null) { - // TODO - BUG - UNDONE - Fix before Whidbey RTM - better message! throw SQL.UDTUnexpectedResult(metaData.udt.AssemblyQualifiedName); } } @@ -2770,7 +2444,7 @@ internal object GetUdtValue(object value, SqlMetaDataPriv metaData, bool returnD { Type t = metaData.udt?.Type; Debug.Assert(t != null, "Unexpected null of udtType on GetUdtValue!"); - o = t.InvokeMember("Null", BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static, null, null, new Object[] { }, CultureInfo.InvariantCulture); + o = t.InvokeMember("Null", BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Static, null, null, Array.Empty(), CultureInfo.InvariantCulture); Debug.Assert(o != null); return o; } @@ -2786,21 +2460,14 @@ internal object GetUdtValue(object value, SqlMetaDataPriv metaData, bool returnD } } - internal byte[] GetBytes(object o) - { - Format format = Format.Native; - int maxSize = 0; - return GetBytes(o, out format, out maxSize); - } - internal byte[] GetBytes(object o, out Format format, out int maxSize) { - SqlUdtInfo attr = AssemblyCache.GetInfoFromType(o.GetType()); + SqlUdtInfo attr = GetInfoFromType(o.GetType()); maxSize = attr.MaxByteSize; format = attr.SerializationFormat; - if (maxSize < -1 || maxSize >= UInt16.MaxValue) - { // Do we need this? Is this the right place? + if (maxSize < -1 || maxSize >= ushort.MaxValue) + { throw new InvalidOperationException(o.GetType() + ": invalid Size"); } @@ -2813,5 +2480,24 @@ internal byte[] GetBytes(object o, out Format format, out int maxSize) } return retval; } - } // SqlConnection -} // Microsoft.Data.SqlClient namespace + + private SqlUdtInfo GetInfoFromType(Type t) + { + Debug.Assert(t != null, "Type object can't be NULL"); + Type orig = t; + do + { + SqlUdtInfo attr = SqlUdtInfo.TryGetFromType(t); + if (attr != null) + { + return attr; + } + + t = t.BaseType; + } + while (t != null); + + throw SQL.UDTInvalidSqlType(orig.AssemblyQualifiedName); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs deleted file mode 100644 index 755e2dd747..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ /dev/null @@ -1,360 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Specialized; -using System.Configuration; -using System.Data.Common; -using System.Diagnostics; -using System.IO; -using System.Runtime.Versioning; -using Microsoft.Data.Common; -using Microsoft.Data.ProviderBase; -using Microsoft.Data.SqlClient.Server; - -namespace Microsoft.Data.SqlClient -{ - sealed internal class SqlConnectionFactory : DbConnectionFactory - { - private SqlConnectionFactory() : base(SqlPerformanceCounters.SingletonInstance) - { - } - - public static readonly SqlConnectionFactory SingletonInstance = new SqlConnectionFactory(); - private const string _metaDataXml = "MetaDataXml"; - - override public DbProviderFactory ProviderFactory - { - get - { - return SqlClientFactory.Instance; - } - } - - override protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection) - { - return CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningConnection, userOptions: null); - } - - override protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) - { - SqlConnectionString opt = (SqlConnectionString)options; - SqlConnectionPoolKey key = (SqlConnectionPoolKey)poolKey; - SqlInternalConnection result = null; - SessionData recoverySessionData = null; - SqlConnection sqlOwningConnection = owningConnection as SqlConnection; - bool applyTransientFaultHandling = sqlOwningConnection != null ? sqlOwningConnection._applyTransientFaultHandling : false; - - SqlConnectionString userOpt = null; - if (userOptions != null) - { - userOpt = (SqlConnectionString)userOptions; - } - else if (sqlOwningConnection != null) - { - userOpt = (SqlConnectionString)(sqlOwningConnection.UserConnectionOptions); - } - - if (sqlOwningConnection != null) - { - recoverySessionData = sqlOwningConnection._recoverySessionData; - } - - if (opt.ContextConnection) - { - result = GetContextConnection(opt, poolGroupProviderInfo); - } - else - { - bool redirectedUserInstance = false; - DbConnectionPoolIdentity identity = null; - - // Pass DbConnectionPoolIdentity to SqlInternalConnectionTds if using integrated security. - // Used by notifications. - if (opt.IntegratedSecurity || opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated) - { - if (pool != null) - { - identity = pool.Identity; - } - else - { - identity = DbConnectionPoolIdentity.GetCurrent(); - } - } - - // FOLLOWING IF BLOCK IS ENTIRELY FOR SSE USER INSTANCES - // If "user instance=true" is in the connection string, we're using SSE user instances - if (opt.UserInstance) - { - // opt.DataSource is used to create the SSE connection - redirectedUserInstance = true; - string instanceName; - - if (pool == null || (pool != null && pool.Count <= 0)) - { // Non-pooled or pooled and no connections in the pool. - - SqlInternalConnectionTds sseConnection = null; - try - { - // What about a failure - throw? YES! - // BUG (VSTFDevDiv) 479687: Using TransactionScope with Linq2SQL against user instances fails with "connection has been broken" message - // NOTE: Cloning connection option opt to set 'UserInstance=True' and 'Enlist=False' - // This first connection is established to SqlExpress to get the instance name - // of the UserInstance. - SqlConnectionString sseopt = new SqlConnectionString(opt, opt.DataSource, true /* user instance=true */, false /* set Enlist = false */); - sseConnection = new SqlInternalConnectionTds(identity, sseopt, key.Credential, null, "", null, false, applyTransientFaultHandling: applyTransientFaultHandling); - // NOTE: Retrieve here. This user instance name will be used below to connect to the Sql Express User Instance. - instanceName = sseConnection.InstanceName; - - // Set future transient fault handling based on connection options - sqlOwningConnection._applyTransientFaultHandling = opt != null && opt.ConnectRetryCount > 0; - - if (!instanceName.StartsWith("\\\\.\\", StringComparison.Ordinal)) - { - throw SQL.NonLocalSSEInstance(); - } - - if (pool != null) - { // Pooled connection - cache result - SqlConnectionPoolProviderInfo providerInfo = (SqlConnectionPoolProviderInfo)pool.ProviderInfo; - // No lock since we are already in creation mutex - providerInfo.InstanceName = instanceName; - } - } - finally - { - if (sseConnection != null) - { - sseConnection.Dispose(); - } - } - } - else - { // Cached info from pool. - SqlConnectionPoolProviderInfo providerInfo = (SqlConnectionPoolProviderInfo)pool.ProviderInfo; - // No lock since we are already in creation mutex - instanceName = providerInfo.InstanceName; - } - - // NOTE: Here connection option opt is cloned to set 'instanceName=' that was - // retrieved from the previous SSE connection. For this UserInstance connection 'Enlist=True'. - // options immutable - stored in global hash - don't modify - opt = new SqlConnectionString(opt, instanceName, false /* user instance=false */, null /* do not modify the Enlist value */); - poolGroupProviderInfo = null; // null so we do not pass to constructor below... - } - result = new SqlInternalConnectionTds(identity, opt, key.Credential, poolGroupProviderInfo, "", null, redirectedUserInstance, userOpt, recoverySessionData, pool, key.AccessToken, applyTransientFaultHandling: applyTransientFaultHandling, key.AccessTokenCallback); - } - return result; - } - - protected override DbConnectionOptions CreateConnectionOptions(string connectionString, DbConnectionOptions previous) - { - Debug.Assert(!ADP.IsEmpty(connectionString), "empty connectionString"); - SqlConnectionString result = new SqlConnectionString(connectionString); - return result; - } - - override internal DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(DbConnectionOptions connectionOptions) - { - DbConnectionPoolProviderInfo providerInfo = null; - - if (((SqlConnectionString)connectionOptions).UserInstance) - { - providerInfo = new SqlConnectionPoolProviderInfo(); - } - - return providerInfo; - } - - override protected DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(DbConnectionOptions connectionOptions) - { - SqlConnectionString opt = (SqlConnectionString)connectionOptions; - - DbConnectionPoolGroupOptions poolingOptions = null; - - if (!opt.ContextConnection && opt.Pooling) - { // never pool context connections. - int connectionTimeout = opt.ConnectTimeout; - - if ((0 < connectionTimeout) && (connectionTimeout < Int32.MaxValue / 1000)) - connectionTimeout *= 1000; - else if (connectionTimeout >= Int32.MaxValue / 1000) - connectionTimeout = Int32.MaxValue; - - if (opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive) - { - // interactive mode will always have pool's CreateTimeout = 10 x ConnectTimeout. - if (connectionTimeout >= Int32.MaxValue / 10) - { - connectionTimeout = Int32.MaxValue; - } - else - { - connectionTimeout *= 10; - } - SqlClientEventSource.Log.TryTraceEvent("Set connection pool CreateTimeout={0} when AD Interactive is in use.", connectionTimeout); - } - - poolingOptions = new DbConnectionPoolGroupOptions( - opt.IntegratedSecurity || opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated, - opt.MinPoolSize, - opt.MaxPoolSize, - connectionTimeout, - opt.LoadBalanceTimeout, - opt.Enlist); - } - return poolingOptions; - } - - override protected DbMetaDataFactory CreateMetaDataFactory(DbConnectionInternal internalConnection, out bool cacheMetaDataFactory) - { - Debug.Assert(internalConnection != null, "internalConnection may not be null."); - - Stream xmlStream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Microsoft.Data.SqlClient.SqlMetaData.xml"); - cacheMetaDataFactory = true; - - Debug.Assert(xmlStream != null, nameof(xmlStream) + " may not be null."); - - return new SqlMetaDataFactory(xmlStream, - internalConnection.ServerVersion, - internalConnection.ServerVersion); //internalConnection.ServerVersionNormalized); - } - - override internal DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo(DbConnectionOptions connectionOptions) - { - return new SqlConnectionPoolGroupProviderInfo((SqlConnectionString)connectionOptions); - } - - - internal static SqlConnectionString FindSqlConnectionOptions(SqlConnectionPoolKey key) - { - SqlConnectionString connectionOptions = (SqlConnectionString)SingletonInstance.FindConnectionOptions(key); - if (connectionOptions == null) - { - connectionOptions = new SqlConnectionString(key.ConnectionString); - } - if (connectionOptions.IsEmpty) - { - throw ADP.NoConnectionString(); - } - return connectionOptions; - } - - private SqlInternalConnectionSmi GetContextConnection(SqlConnectionString options, object providerInfo) - { - SmiContext smiContext = SmiContextFactory.Instance.GetCurrentContext(); - - SqlInternalConnectionSmi result = (SqlInternalConnectionSmi)smiContext.GetContextValue((int)SmiContextFactory.ContextKey.Connection); - - // context connections are automatically re-useable if they exist unless they've been doomed. - if (result == null || result.IsConnectionDoomed) - { - if (result != null) - { - result.Dispose(); // A doomed connection is a messy thing. Dispose of it promptly in nearest receptacle. - } - - result = new SqlInternalConnectionSmi(options, smiContext); - smiContext.SetContextValue((int)SmiContextFactory.ContextKey.Connection, result); - } - - result.Activate(); - - return result; - } - - override internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnection connection) - { - SqlConnection c = (connection as SqlConnection); - if (c != null) - { - return c.PoolGroup; - } - return null; - } - - override internal DbConnectionInternal GetInnerConnection(DbConnection connection) - { - SqlConnection c = (connection as SqlConnection); - if (c != null) - { - return c.InnerConnection; - } - return null; - } - - override protected int GetObjectId(DbConnection connection) - { - SqlConnection c = (connection as SqlConnection); - if (c != null) - { - return c.ObjectID; - } - return 0; - } - - override internal void PermissionDemand(DbConnection outerConnection) - { - SqlConnection c = (outerConnection as SqlConnection); - if (c != null) - { - c.PermissionDemand(); - } - } - - override internal void SetConnectionPoolGroup(DbConnection outerConnection, DbConnectionPoolGroup poolGroup) - { - SqlConnection c = (outerConnection as SqlConnection); - if (c != null) - { - c.PoolGroup = poolGroup; - } - } - - override internal void SetInnerConnectionEvent(DbConnection owningObject, DbConnectionInternal to) - { - SqlConnection c = (owningObject as SqlConnection); - if (c != null) - { - c.SetInnerConnectionEvent(to); - } - } - - override internal bool SetInnerConnectionFrom(DbConnection owningObject, DbConnectionInternal to, DbConnectionInternal from) - { - SqlConnection c = (owningObject as SqlConnection); - if (c != null) - { - return c.SetInnerConnectionFrom(to, from); - } - return false; - } - - override internal void SetInnerConnectionTo(DbConnection owningObject, DbConnectionInternal to) - { - SqlConnection c = (owningObject as SqlConnection); - if (c != null) - { - c.SetInnerConnectionTo(to); - } - } - - } - - [System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Name = "FullTrust")] - sealed internal class SqlPerformanceCounters : DbConnectionPoolCounters - { - private const string CategoryName = ".NET Data Provider for SqlServer"; - private const string CategoryHelp = "Counters for Microsoft.Data.SqlClient"; - - public static readonly SqlPerformanceCounters SingletonInstance = new SqlPerformanceCounters(); - - [System.Diagnostics.PerformanceCounterPermissionAttribute(System.Security.Permissions.SecurityAction.Assert, PermissionAccess = PerformanceCounterPermissionAccess.Write, MachineName = ".", CategoryName = CategoryName)] - private SqlPerformanceCounters() : base(CategoryName, CategoryHelp) - { - } - } -} - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs index 54d31d5301..eb5cb511be 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionHelper.cs @@ -2,18 +2,20 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; +using System.Data; +using System.Data.Common; +using System.Diagnostics; +using System.Runtime.ConstrainedExecution; +using System.Threading; +using System.Transactions; +using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; + namespace Microsoft.Data.SqlClient { - using System; - using System.Data; - using System.Data.Common; - using System.Diagnostics; - using System.Runtime.ConstrainedExecution; - using System.Threading; - using Microsoft.Data.Common; - using Microsoft.Data.ProviderBase; - using System.Transactions; - public sealed partial class SqlConnection : DbConnection { private static readonly DbConnectionFactory _connectionFactory = SqlConnectionFactory.SingletonInstance; @@ -79,7 +81,7 @@ internal DbConnectionOptions ConnectionOptions { get { - Microsoft.Data.ProviderBase.DbConnectionPoolGroup poolGroup = PoolGroup; + DbConnectionPoolGroup poolGroup = PoolGroup; return poolGroup != null ? poolGroup.ConnectionOptions : null; } } @@ -102,7 +104,7 @@ private void ConnectionString_Set(string value) private void ConnectionString_Set(DbConnectionPoolKey key) { DbConnectionOptions connectionOptions = null; - Microsoft.Data.ProviderBase.DbConnectionPoolGroup poolGroup = ConnectionFactory.GetConnectionPoolGroup(key, null, ref connectionOptions); + DbConnectionPoolGroup poolGroup = ConnectionFactory.GetConnectionPoolGroup(key, null, ref connectionOptions); DbConnectionInternal connectionInternal = InnerConnection; bool flag = connectionInternal.AllowSetConnectionString; if (flag) @@ -144,7 +146,7 @@ internal DbConnectionInternal InnerConnection } } - internal Microsoft.Data.ProviderBase.DbConnectionPoolGroup PoolGroup + internal DbConnectionPoolGroup PoolGroup { get { @@ -341,7 +343,7 @@ internal void PermissionDemand() { Debug.Assert(DbConnectionClosedConnecting.SingletonInstance == _innerConnection, "not connecting"); - Microsoft.Data.ProviderBase.DbConnectionPoolGroup poolGroup = PoolGroup; + DbConnectionPoolGroup poolGroup = PoolGroup; DbConnectionOptions connectionOptions = poolGroup != null ? poolGroup.ConnectionOptions : null; if (connectionOptions == null || connectionOptions.IsEmpty) { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs deleted file mode 100644 index 2cd5370c8e..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ /dev/null @@ -1,6364 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Data.Common; -using System.Data.SqlTypes; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Text; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using Microsoft.Data.Common; -using Microsoft.Data.ProviderBase; -using Microsoft.Data.SqlClient.DataClassification; -using Microsoft.Data.SqlClient.Server; -using Microsoft.Data.SqlTypes; - -namespace Microsoft.Data.SqlClient -{ - /// - public class SqlDataReader : DbDataReader, IDataReader - { - private enum ALTROWSTATUS - { - Null = 0, // default and after Done - AltRow, // after calling NextResult and the first AltRow is available for read - Done, // after consuming the value (GetValue -> GetValueInternal) - } - - internal class SharedState - { // parameters needed to execute cleanup from parser - internal int _nextColumnHeaderToRead; - internal int _nextColumnDataToRead; - internal long _columnDataBytesRemaining; - internal bool _dataReady; // ready to ProcessRow - } - - internal SharedState _sharedState = new SharedState(); - - private TdsParser _parser; // TODO: Probably don't need this, since it's on the stateObj - private TdsParserStateObject _stateObj; - private SqlCommand _command; - private SqlConnection _connection; - private int _defaultLCID; - private bool _haltRead; // bool to denote whether we have read first row for single row behavior - private bool _metaDataConsumed; - private bool _browseModeInfoConsumed; - private bool _isClosed; - private bool _isInitialized; // Webdata 104560 - private bool _hasRows; - private ALTROWSTATUS _altRowStatus; - private int _recordsAffected = -1; - private long _defaultTimeoutMilliseconds; - private SqlConnectionString.TypeSystem _typeSystem; - - // SQLStatistics support - private SqlStatistics _statistics; - private SqlBuffer[] _data; // row buffer, filled in by ReadColumnData() - private SqlStreamingXml _streamingXml; // Used by Getchars on an Xml column for sequential access - - // buffers and metadata - private _SqlMetaDataSet _metaData; // current metaData for the stream, it is lazily loaded - private _SqlMetaDataSetCollection _altMetaDataSetCollection; - private FieldNameLookup _fieldNameLookup; - private CommandBehavior _commandBehavior; - - private static int s_objectTypeCount; // EventSource Counter - internal readonly int ObjectID = Interlocked.Increment(ref s_objectTypeCount); - - - // metadata (no explicit table, use 'Table') - private MultiPartTableName[] _tableNames = null; - private string _resetOptionsString; - - private int _lastColumnWithDataChunkRead; - private long _columnDataBytesRead; // last byte read by user - private long _columnDataCharsRead; // last char read by user - private char[] _columnDataChars; - private int _columnDataCharsIndex; // Column index that is currently loaded in _columnDataChars - - private Task _currentTask; - private Snapshot _snapshot; - private CancellationTokenSource _cancelAsyncOnCloseTokenSource; - private CancellationToken _cancelAsyncOnCloseToken; - - private SqlSequentialStream _currentStream; - private SqlSequentialTextReader _currentTextReader; - - private IsDBNullAsyncCallContext _cachedIsDBNullContext; - private ReadAsyncCallContext _cachedReadAsyncContext; - - internal SqlDataReader(SqlCommand command, CommandBehavior behavior) - { - SqlConnection.VerifyExecutePermission(); - - _command = command; - _commandBehavior = behavior; - if (_command != null) - { - _defaultTimeoutMilliseconds = (long)command.CommandTimeout * 1000L; - _connection = command.Connection; - if (_connection != null) - { - _statistics = _connection.Statistics; - _typeSystem = _connection.TypeSystem; - } - } - _sharedState._dataReady = false; - _metaDataConsumed = false; - _hasRows = false; - _browseModeInfoConsumed = false; - _currentStream = null; - _currentTextReader = null; - _cancelAsyncOnCloseTokenSource = new CancellationTokenSource(); - _cancelAsyncOnCloseToken = _cancelAsyncOnCloseTokenSource.Token; - _columnDataCharsIndex = -1; - } - - internal bool BrowseModeInfoConsumed - { - set - { - _browseModeInfoConsumed = value; - } - } - - internal SqlCommand Command - { - get - { - return _command; - } - } - - /// - protected SqlConnection Connection - { - get - { - return _connection; - } - } - - /// - public SensitivityClassification SensitivityClassification { get; internal set; } - - /// - override public int Depth - { - get - { - if (this.IsClosed) - { - throw ADP.DataReaderClosed(); - } - - return 0; - } - } - - /// - // fields/attributes collection - override public int FieldCount - { - get - { - if (this.IsClosed) - { - throw ADP.DataReaderClosed(); - } - if (_currentTask != null) - { - throw ADP.AsyncOperationPending(); - } - - if (MetaData == null) - { - return 0; - } - - return _metaData.Length; - } - } - - /// - override public bool HasRows - { - get - { - if (this.IsClosed) - { - throw ADP.DataReaderClosed(); - } - if (_currentTask != null) - { - throw ADP.AsyncOperationPending(); - } - - return _hasRows; - } - } - - /// - override public bool IsClosed - { - get - { - return _isClosed; - } - } - - internal bool IsInitialized - { - get - { - return _isInitialized; - } - set - { - Debug.Assert(value, "attempting to uninitialize a data reader?"); - _isInitialized = value; - } - } - - // NOTE: For PLP values this indicates the amount of data left in the current chunk (or 0 if there are no more chunks left) - internal long ColumnDataBytesRemaining() - { - // If there are an unknown (-1) number of bytes left for a PLP, read its size - if (-1 == _sharedState._columnDataBytesRemaining) - { - _sharedState._columnDataBytesRemaining = (long)_parser.PlpBytesLeft(_stateObj); - } - - return _sharedState._columnDataBytesRemaining; - } - - internal _SqlMetaDataSet MetaData - { - get - { - if (IsClosed) - { - throw ADP.DataReaderClosed(); - } - // metaData comes in pieces: colmetadata, tabname, colinfo, etc - // if we have any metaData, return it. If we have none, - // then fetch it - if (_metaData == null && !_metaDataConsumed) - { - if (_currentTask != null) - { - throw SQL.PendingBeginXXXExists(); - } - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - - Debug.Assert(_stateObj == null || _stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - if (TryConsumeMetaData() != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.StackOverflowException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.Threading.ThreadAbortException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - } - return _metaData; - } - } - - internal virtual SmiExtendedMetaData[] GetInternalSmiMetaData() - { - SmiExtendedMetaData[] metaDataReturn = null; - _SqlMetaDataSet metaData = this.MetaData; - - if (metaData != null && 0 < metaData.Length) - { - metaDataReturn = new SmiExtendedMetaData[metaData.VisibleColumnCount]; - int returnIndex = 0; - for (int index = 0; index < metaData.Length; index++) - { - _SqlMetaData colMetaData = metaData[index]; - - if (!colMetaData.IsHidden) - { - SqlCollation collation = colMetaData.collation; - - string typeSpecificNamePart1 = null; - string typeSpecificNamePart2 = null; - string typeSpecificNamePart3 = null; - - if (SqlDbType.Xml == colMetaData.type) - { - typeSpecificNamePart1 = colMetaData.xmlSchemaCollection?.Database; - typeSpecificNamePart2 = colMetaData.xmlSchemaCollection?.OwningSchema; - typeSpecificNamePart3 = colMetaData.xmlSchemaCollection?.Name; - } - else if (SqlDbType.Udt == colMetaData.type) - { - Connection.CheckGetExtendedUDTInfo(colMetaData, true); // Ensure that colMetaData.udtType is set - - typeSpecificNamePart1 = colMetaData.udt?.DatabaseName; - typeSpecificNamePart2 = colMetaData.udt?.SchemaName; - typeSpecificNamePart3 = colMetaData.udt?.TypeName; - } - - int length = colMetaData.length; - if (length > TdsEnums.MAXSIZE) - { - length = (int)SmiMetaData.UnlimitedMaxLengthIndicator; - } - else if (SqlDbType.NChar == colMetaData.type - || SqlDbType.NVarChar == colMetaData.type) - { - length /= ADP.CharSize; - } - - metaDataReturn[returnIndex] = - new SmiQueryMetaData( - colMetaData.type, - length, - colMetaData.precision, - colMetaData.scale, - collation != null ? collation.LCID : _defaultLCID, - collation != null ? collation.SqlCompareOptions : SqlCompareOptions.None, - colMetaData.udt?.Type, - isMultiValued: false, - fieldMetaData: null, - extendedProperties: null, - colMetaData.column, - typeSpecificNamePart1, - typeSpecificNamePart2, - typeSpecificNamePart3, - colMetaData.IsNullable, - colMetaData.serverName, - colMetaData.catalogName, - colMetaData.schemaName, - colMetaData.tableName, - colMetaData.baseColumn, - colMetaData.IsKey, - colMetaData.IsIdentity, - colMetaData.IsReadOnly, - colMetaData.IsExpression, - colMetaData.IsDifferentName, - colMetaData.IsHidden); - - returnIndex += 1; - } - } - } - - return metaDataReturn; - } - - /// - override public int RecordsAffected - { - get - { - if (_command != null) - { - return _command.InternalRecordsAffected; - } - - // cached locally for after Close() when command is nulled out - return _recordsAffected; - } - } - - internal string ResetOptionsString - { - set - { - _resetOptionsString = value; - } - } - - private SqlStatistics Statistics - { - get - { - return _statistics; - } - } - - internal MultiPartTableName[] TableNames - { - get - { - return _tableNames; - } - set - { - _tableNames = value; - } - } - - /// - override public int VisibleFieldCount - { - get - { - if (this.IsClosed) - { - throw ADP.DataReaderClosed(); - } - _SqlMetaDataSet md = this.MetaData; - if (md == null) - { - return 0; - } - return md.VisibleColumnCount; - } - } - - /// - // this operator - override public object this[int i] - { - get - { - return GetValue(i); - } - } - - /// - override public object this[string name] - { - get - { - return GetValue(GetOrdinal(name)); - } - } - - internal void Bind(TdsParserStateObject stateObj) - { - Debug.Assert(stateObj != null, "null stateobject"); - - Debug.Assert(_snapshot == null, "Should not change during execution of asynchronous command"); - - stateObj.Owner = this; - _stateObj = stateObj; - _parser = stateObj.Parser; - _defaultLCID = _parser.DefaultLCID; - } - -#if !NETFRAMEWORK - [SuppressMessage("ReflectionAnalysis", "IL2111", - Justification = "System.Type.TypeInitializer would not be used in dataType and providerSpecificDataType columns.")] -#endif - internal DataTable BuildSchemaTable() - { - _SqlMetaDataSet md = this.MetaData; - Debug.Assert(md != null, "BuildSchemaTable - unexpected null metadata information"); - - DataTable schemaTable = new DataTable("SchemaTable"); - schemaTable.Locale = CultureInfo.InvariantCulture; - schemaTable.MinimumCapacity = md.Length; - - DataColumn columnName = new DataColumn(SchemaTableColumn.ColumnName, typeof(string)); - DataColumn ordinal = new DataColumn(SchemaTableColumn.ColumnOrdinal, typeof(int)); - DataColumn size = new DataColumn(SchemaTableColumn.ColumnSize, typeof(int)); - DataColumn precision = new DataColumn(SchemaTableColumn.NumericPrecision, typeof(short)); - DataColumn scale = new DataColumn(SchemaTableColumn.NumericScale, typeof(short)); - - DataColumn dataType = new DataColumn(SchemaTableColumn.DataType, typeof(System.Type)); - DataColumn providerSpecificDataType = new DataColumn(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(System.Type)); - DataColumn nonVersionedProviderType = new DataColumn(SchemaTableColumn.NonVersionedProviderType, typeof(int)); - DataColumn providerType = new DataColumn(SchemaTableColumn.ProviderType, typeof(int)); - - DataColumn isLong = new DataColumn(SchemaTableColumn.IsLong, typeof(bool)); - DataColumn allowDBNull = new DataColumn(SchemaTableColumn.AllowDBNull, typeof(bool)); - DataColumn isReadOnly = new DataColumn(SchemaTableOptionalColumn.IsReadOnly, typeof(bool)); - DataColumn isRowVersion = new DataColumn(SchemaTableOptionalColumn.IsRowVersion, typeof(bool)); - - DataColumn isUnique = new DataColumn(SchemaTableColumn.IsUnique, typeof(bool)); - DataColumn isKey = new DataColumn(SchemaTableColumn.IsKey, typeof(bool)); - DataColumn isAutoIncrement = new DataColumn(SchemaTableOptionalColumn.IsAutoIncrement, typeof(bool)); - DataColumn isHidden = new DataColumn(SchemaTableOptionalColumn.IsHidden, typeof(bool)); - - DataColumn baseCatalogName = new DataColumn(SchemaTableOptionalColumn.BaseCatalogName, typeof(string)); - DataColumn baseSchemaName = new DataColumn(SchemaTableColumn.BaseSchemaName, typeof(string)); - DataColumn baseTableName = new DataColumn(SchemaTableColumn.BaseTableName, typeof(string)); - DataColumn baseColumnName = new DataColumn(SchemaTableColumn.BaseColumnName, typeof(string)); - - // unique to SqlClient - DataColumn baseServerName = new DataColumn(SchemaTableOptionalColumn.BaseServerName, typeof(string)); - DataColumn isAliased = new DataColumn(SchemaTableColumn.IsAliased, typeof(bool)); - DataColumn isExpression = new DataColumn(SchemaTableColumn.IsExpression, typeof(bool)); - DataColumn isIdentity = new DataColumn("IsIdentity", typeof(bool)); - DataColumn dataTypeName = new DataColumn("DataTypeName", typeof(string)); - DataColumn udtAssemblyQualifiedName = new DataColumn("UdtAssemblyQualifiedName", typeof(string)); - // Xml metadata specific - DataColumn xmlSchemaCollectionDatabase = new DataColumn("XmlSchemaCollectionDatabase", typeof(string)); - DataColumn xmlSchemaCollectionOwningSchema = new DataColumn("XmlSchemaCollectionOwningSchema", typeof(string)); - DataColumn xmlSchemaCollectionName = new DataColumn("XmlSchemaCollectionName", typeof(string)); - // SparseColumnSet - DataColumn isColumnSet = new DataColumn("IsColumnSet", typeof(bool)); - - ordinal.DefaultValue = 0; - isLong.DefaultValue = false; - - DataColumnCollection columns = schemaTable.Columns; - - // must maintain order for backward compatibility - columns.Add(columnName); - columns.Add(ordinal); - columns.Add(size); - columns.Add(precision); - columns.Add(scale); - columns.Add(isUnique); - columns.Add(isKey); - columns.Add(baseServerName); - columns.Add(baseCatalogName); - columns.Add(baseColumnName); - columns.Add(baseSchemaName); - columns.Add(baseTableName); - columns.Add(dataType); - columns.Add(allowDBNull); - columns.Add(providerType); - columns.Add(isAliased); - columns.Add(isExpression); - columns.Add(isIdentity); - columns.Add(isAutoIncrement); - columns.Add(isRowVersion); - columns.Add(isHidden); - columns.Add(isLong); - columns.Add(isReadOnly); - columns.Add(providerSpecificDataType); - columns.Add(dataTypeName); - columns.Add(xmlSchemaCollectionDatabase); - columns.Add(xmlSchemaCollectionOwningSchema); - columns.Add(xmlSchemaCollectionName); - columns.Add(udtAssemblyQualifiedName); - columns.Add(nonVersionedProviderType); - columns.Add(isColumnSet); - - for (int i = 0; i < md.Length; i++) - { - _SqlMetaData col = md[i]; - DataRow schemaRow = schemaTable.NewRow(); - - schemaRow[columnName] = col.column; - schemaRow[ordinal] = col.ordinal; - // - // be sure to return character count for string types, byte count otherwise - // col.length is always byte count so for unicode types, half the length - // - // For MAX and XML datatypes, we get 0x7fffffff from the server. Do not divide this. - if (col.cipherMD != null) - { - Debug.Assert(col.baseTI != null && col.baseTI.metaType != null, "col.baseTI and col.baseTI.metaType should not be null."); - schemaRow[size] = (col.baseTI.metaType.IsSizeInCharacters && (col.baseTI.length != 0x7fffffff)) ? (col.baseTI.length / 2) : col.baseTI.length; - } - else - { - schemaRow[size] = (col.metaType.IsSizeInCharacters && (col.length != 0x7fffffff)) ? (col.length / 2) : col.length; - } - - schemaRow[dataType] = GetFieldTypeInternal(col); - schemaRow[providerSpecificDataType] = GetProviderSpecificFieldTypeInternal(col); - schemaRow[nonVersionedProviderType] = (int)(col.cipherMD != null ? col.baseTI.type : col.type); // SqlDbType enum value - does not change with TypeSystem. - schemaRow[dataTypeName] = GetDataTypeNameInternal(col); - - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && col.Is2008DateTimeType) - { - schemaRow[providerType] = SqlDbType.NVarChar; - switch (col.type) - { - case SqlDbType.Date: - schemaRow[size] = TdsEnums.WHIDBEY_DATE_LENGTH; - break; - case SqlDbType.Time: - Debug.Assert(TdsEnums.UNKNOWN_PRECISION_SCALE == col.scale || (0 <= col.scale && col.scale <= 7), "Invalid scale for Time column: " + col.scale); - schemaRow[size] = TdsEnums.WHIDBEY_TIME_LENGTH[TdsEnums.UNKNOWN_PRECISION_SCALE != col.scale ? col.scale : col.metaType.Scale]; - break; - case SqlDbType.DateTime2: - Debug.Assert(TdsEnums.UNKNOWN_PRECISION_SCALE == col.scale || (0 <= col.scale && col.scale <= 7), "Invalid scale for DateTime2 column: " + col.scale); - schemaRow[size] = TdsEnums.WHIDBEY_DATETIME2_LENGTH[TdsEnums.UNKNOWN_PRECISION_SCALE != col.scale ? col.scale : col.metaType.Scale]; - break; - case SqlDbType.DateTimeOffset: - Debug.Assert(TdsEnums.UNKNOWN_PRECISION_SCALE == col.scale || (0 <= col.scale && col.scale <= 7), "Invalid scale for DateTimeOffset column: " + col.scale); - schemaRow[size] = TdsEnums.WHIDBEY_DATETIMEOFFSET_LENGTH[TdsEnums.UNKNOWN_PRECISION_SCALE != col.scale ? col.scale : col.metaType.Scale]; - break; - } - } - else if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && col.IsLargeUdt) - { - if (_typeSystem == SqlConnectionString.TypeSystem.SQLServer2005) - { - schemaRow[providerType] = SqlDbType.VarBinary; - } - else - { - // TypeSystem.SQLServer2000 - schemaRow[providerType] = SqlDbType.Image; - } - } - else if (_typeSystem != SqlConnectionString.TypeSystem.SQLServer2000) - { - // TypeSystem.SQLServer2005 and above - - // SqlDbType enum value - always the actual type for SQLServer2005. - schemaRow[providerType] = (int)(col.cipherMD != null ? col.baseTI.type : col.type); - - if (col.type == SqlDbType.Udt) - { // Additional metadata for UDTs. - Debug.Assert(Connection.Is2005OrNewer, "Invalid Column type received from the server"); - schemaRow[udtAssemblyQualifiedName] = col.udt?.AssemblyQualifiedName; - } - else if (col.type == SqlDbType.Xml) - { // Additional metadata for Xml. - Debug.Assert(Connection.Is2005OrNewer, "Invalid DataType (Xml) for the column"); - schemaRow[xmlSchemaCollectionDatabase] = col.xmlSchemaCollection?.Database; - schemaRow[xmlSchemaCollectionOwningSchema] = col.xmlSchemaCollection?.OwningSchema; - schemaRow[xmlSchemaCollectionName] = col.xmlSchemaCollection?.Name; - } - } - else - { - // TypeSystem.SQLServer2000 - - // SqlDbType enum value - variable for certain types when SQLServer2000. - schemaRow[providerType] = GetVersionedMetaType(col.metaType).SqlDbType; - } - - if (col.cipherMD != null) - { - Debug.Assert(col.baseTI != null, @"col.baseTI should not be null."); - if (TdsEnums.UNKNOWN_PRECISION_SCALE != col.baseTI.precision) - { - schemaRow[precision] = col.baseTI.precision; - } - else - { - schemaRow[precision] = col.baseTI.metaType.Precision; - } - } - else if (TdsEnums.UNKNOWN_PRECISION_SCALE != col.precision) - { - schemaRow[precision] = col.precision; - } - else - { - schemaRow[precision] = col.metaType.Precision; - } - - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && col.Is2008DateTimeType) - { - schemaRow[scale] = MetaType.MetaNVarChar.Scale; - } - else if (col.cipherMD != null) - { - Debug.Assert(col.baseTI != null, @"col.baseTI should not be null."); - if (TdsEnums.UNKNOWN_PRECISION_SCALE != col.baseTI.scale) - { - schemaRow[scale] = col.baseTI.scale; - } - else - { - schemaRow[scale] = col.baseTI.metaType.Scale; - } - } - else if (TdsEnums.UNKNOWN_PRECISION_SCALE != col.scale) - { - schemaRow[scale] = col.scale; - } - else - { - schemaRow[scale] = col.metaType.Scale; - } - - schemaRow[allowDBNull] = col.IsNullable; - - // If no ColInfo token received, do not set value, leave as null. - if (_browseModeInfoConsumed) - { - schemaRow[isAliased] = col.IsDifferentName; - schemaRow[isKey] = col.IsKey; - schemaRow[isHidden] = col.IsHidden; - schemaRow[isExpression] = col.IsExpression; - } - - schemaRow[isIdentity] = col.IsIdentity; - schemaRow[isAutoIncrement] = col.IsIdentity; - - if (col.cipherMD != null) - { - Debug.Assert(col.baseTI != null, @"col.baseTI should not be null."); - Debug.Assert(col.baseTI.metaType != null, @"col.baseTI.metaType should not be null."); - schemaRow[isLong] = col.baseTI.metaType.IsLong; - } - else - { - schemaRow[isLong] = col.metaType.IsLong; - } - - // mark unique for timestamp columns - if (SqlDbType.Timestamp == col.type) - { - schemaRow[isUnique] = true; - schemaRow[isRowVersion] = true; - } - else - { - schemaRow[isUnique] = false; - schemaRow[isRowVersion] = false; - } - - schemaRow[isReadOnly] = col.IsReadOnly; - schemaRow[isColumnSet] = col.IsColumnSet; - - if (!string.IsNullOrEmpty(col.serverName)) - { - schemaRow[baseServerName] = col.serverName; - } - if (!string.IsNullOrEmpty(col.catalogName)) - { - schemaRow[baseCatalogName] = col.catalogName; - } - if (!string.IsNullOrEmpty(col.schemaName)) - { - schemaRow[baseSchemaName] = col.schemaName; - } - if (!string.IsNullOrEmpty(col.tableName)) - { - schemaRow[baseTableName] = col.tableName; - } - if (!string.IsNullOrEmpty(col.baseColumn)) - { - schemaRow[baseColumnName] = col.baseColumn; - } - else if (!string.IsNullOrEmpty(col.column)) - { - schemaRow[baseColumnName] = col.column; - } - - schemaTable.Rows.Add(schemaRow); - schemaRow.AcceptChanges(); - } - - // mark all columns as readonly - foreach (DataColumn column in columns) - { - column.ReadOnly = true; // MDAC 70943 - } - - return schemaTable; - } - - internal void Cancel(int objectID) - { - TdsParserStateObject stateObj = _stateObj; - if (stateObj != null) - { - stateObj.Cancel(objectID); - } - } - - // wipe any data off the wire from a partial read - // and reset all pointers for sequential access - private TdsOperationStatus TryCleanPartialRead() - { - AssertReaderState(requireData: true, permitAsync: true); - - // VSTS DEVDIV2 380446: It is possible that read attempt we are cleaning after ended with partially - // processed header (if it falls between network packets). In this case the first thing to do is to - // finish reading the header, otherwise code will start treating unread header as TDS payload. - TdsOperationStatus result; - if (_stateObj._partialHeaderBytesRead > 0) - { - result = _stateObj.TryProcessHeader(); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - - // following cases for sequential read - // i. user called read but didn't fetch anything - // iia. user called read and fetched a subset of the columns - // iib. user called read and fetched a subset of the column data - - // Wipe out any Streams or TextReaders - if (-1 != _lastColumnWithDataChunkRead) - { - CloseActiveSequentialStreamAndTextReader(); - } - - // i. user called read but didn't fetch anything - if (0 == _sharedState._nextColumnHeaderToRead) - { - result = _stateObj.Parser.TrySkipRow(_metaData, _stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - else - { - // iia. if we still have bytes left from a partially read column, skip - result = TryResetBlobState(); - if (result != TdsOperationStatus.Done) - { - return result; - } - - // iib. - // now read the remaining values off the wire for this row - result = _stateObj.Parser.TrySkipRow(_metaData, _sharedState._nextColumnHeaderToRead, _stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - -#if DEBUG - if (_stateObj.HasPendingData) - { - byte token; - result = _stateObj.TryPeekByte(out token); - if (result != TdsOperationStatus.Done) - { - return result; - } - - Debug.Assert(TdsParser.IsValidTdsToken(token), string.Format("Invalid token after performing CleanPartialRead: {0,-2:X2}", token)); - } -#endif - _sharedState._dataReady = false; - - return TdsOperationStatus.Done; - } - - private void CleanPartialReadReliable() - { - AssertReaderState(requireData: true, permitAsync: false); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - TdsOperationStatus result = TryCleanPartialRead(); - Debug.Assert(result == TdsOperationStatus.Done, "Should not pend on sync call"); - Debug.Assert(!_sharedState._dataReady, "_dataReady should be cleared"); - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.StackOverflowException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.Threading.ThreadAbortException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - } - - /// - protected override void Dispose(bool disposing) - { - try - { - if (disposing) - { - Close(); - } - base.Dispose(disposing); - } - catch (SqlException ex) - { - SqlClientEventSource.Log.TryTraceEvent("SqlDataReader.Dispose | ERR | Error Message: {0}, Stack Trace: {1}", ex.Message, ex.StackTrace); - } - } - - /// - public override void Close() - { - using (TryEventScope.Create(" {0}", ObjectID)) - { - SqlStatistics statistics = null; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - TdsParserStateObject stateObj = _stateObj; - - // Request that the current task is stopped - _cancelAsyncOnCloseTokenSource.Cancel(); - var currentTask = _currentTask; - if ((currentTask != null) && (!currentTask.IsCompleted)) - { - try - { - // Wait for the task to complete - ((IAsyncResult)currentTask).AsyncWaitHandle.WaitOne(); - - // Ensure that we've finished reading any pending data - var networkPacketTaskSource = stateObj._networkPacketTaskSource; - if (networkPacketTaskSource != null) - { - ((IAsyncResult)networkPacketTaskSource.Task).AsyncWaitHandle.WaitOne(); - } - } - catch (Exception) - { - // If we receive any exceptions while waiting, something has gone horribly wrong and we need to doom the connection and fast-fail the reader - _connection.InnerConnection.DoomThisConnection(); - _isClosed = true; - - if (stateObj != null) - { - lock (stateObj) - { - _stateObj = null; - _command = null; - _connection = null; - } - } - - throw; - } - } - - // Close down any active Streams and TextReaders (this will also wait for them to finish their async tasks) - // NOTE: This must be done outside of the lock on the stateObj otherwise it will deadlock with CleanupAfterAsyncInvocation - CloseActiveSequentialStreamAndTextReader(); - - if (stateObj != null) - { - // protect against concurrent close and cancel - lock (stateObj) - { - if (_stateObj != null) - { // reader not closed while we waited for the lock - // TryCloseInternal will clear out the snapshot when it is done - if (_snapshot != null) - { -#if DEBUG - // The stack trace for replays will differ since they weren't captured during close - stateObj._permitReplayStackTraceToDiffer = true; -#endif - PrepareForAsyncContinuation(); - } - - SetTimeout(_defaultTimeoutMilliseconds); - - // Close can be called from async methods in error cases, - // in which case we need to switch to syncOverAsync - stateObj._syncOverAsync = true; - - if (TryCloseInternal(closeReader: true) != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - // DO NOT USE stateObj after this point - it has been returned to the TdsParser's session pool and potentially handed out to another thread - } - } - } - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - } - - private TdsOperationStatus TryCloseInternal(bool closeReader) - { - TdsParser parser = _parser; - TdsParserStateObject stateObj = _stateObj; - bool closeConnection = (IsCommandBehavior(CommandBehavior.CloseConnection)); - bool aborting = false; - bool cleanDataFailed = false; - TdsOperationStatus result; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - if ((!_isClosed) && (parser != null) && (stateObj != null) && (stateObj.HasPendingData)) - { - // It is possible for this to be called during connection close on a - // broken connection, so check state first. - if (parser.State == TdsParserState.OpenLoggedIn) - { - // if user called read but didn't fetch any values, skip the row - // same applies after NextResult on ALTROW because NextResult starts rowconsumption in that case ... - - Debug.Assert(SniContext.Snix_Read == stateObj.SniContext, String.Format((IFormatProvider)null, "The SniContext should be Snix_Read but it actually is {0}", stateObj.SniContext)); - - if (_altRowStatus == ALTROWSTATUS.AltRow) - { - _sharedState._dataReady = true; // set _sharedState._dataReady to not confuse CleanPartialRead - } - _stateObj.SetTimeoutStateStopped(); - if (_sharedState._dataReady) - { - cleanDataFailed = true; - result = TryCleanPartialRead(); - if (result == TdsOperationStatus.Done) - { - cleanDataFailed = false; - } - else - { - return result; - } - } -#if DEBUG - else - { - byte token; - result = _stateObj.TryPeekByte(out token); - if (result != TdsOperationStatus.Done) - { - return result; - } - - Debug.Assert(TdsParser.IsValidTdsToken(token), $"DataReady is false, but next token is invalid: {token,-2:X2}"); - } -#endif - - - result = parser.TryRun(RunBehavior.Clean, _command, this, null, stateObj, out _); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - } - - RestoreServerSettings(parser, stateObj); - return TdsOperationStatus.Done; - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - _isClosed = true; - aborting = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.StackOverflowException e) - { - _isClosed = true; - aborting = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.Threading.ThreadAbortException e) - { - _isClosed = true; - aborting = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - finally - { - if (aborting) - { - _isClosed = true; - _command = null; // we are done at this point, don't allow navigation to the connection - _connection = null; - _statistics = null; - _stateObj = null; - _parser = null; - } - else if (closeReader) - { - bool wasClosed = _isClosed; - _isClosed = true; - _parser = null; - _stateObj = null; - _data = null; - - if (_snapshot != null) - { - CleanupAfterAsyncInvocationInternal(stateObj); - } - - // Note the order here is extremely important: - // - // (1) First, we remove the reader from the reference collection - // to prevent it from being forced closed by the parser if - // any future work occurs. - // - // (2) Next, we ensure that cancellation can no longer happen by - // calling CloseSession. - - if (Connection != null) - { - Connection.RemoveWeakReference(this); // This doesn't catch everything -- the connection may be closed, but it prevents dead readers from clogging the collection - } - - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - // IsClosed may be true if CloseReaderFromConnection was called - in which case, the session has already been closed - if (!wasClosed && stateObj != null) - { - if (!cleanDataFailed) - { - stateObj.CloseSession(); - } - else - { - if (parser != null) - { - parser.State = TdsParserState.Broken; // We failed while draining data, so TDS pointer can be between tokens - cannot recover - parser.PutSession(stateObj); - parser.Connection.BreakConnection(); - } - } - } - // DO NOT USE stateObj after this point - it has been returned to the TdsParser's session pool and potentially handed out to another thread - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.StackOverflowException e) - { - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.Threading.ThreadAbortException e) - { - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - // DO NOT USE stateObj after this point - it has been returned to the TdsParser's session pool and potentially handed out to another thread - - // do not retry here - result = TrySetMetaData(null, false); - Debug.Assert(result == TdsOperationStatus.Done, "Should not pend a synchronous request"); - _fieldNameLookup = null; - - // if the user calls ExecuteReader(CommandBehavior.CloseConnection) - // then we close down the connection when we are done reading results - if (closeConnection) - { - if (Connection != null) - { - Connection.Close(); - } - } - if (_command != null) - { - // cache recordsaffected to be returnable after DataReader.Close(); - _recordsAffected = _command.InternalRecordsAffected; - } - - _command = null; // we are done at this point, don't allow navigation to the connection - _connection = null; - _statistics = null; - } - } - } - - virtual internal void CloseReaderFromConnection() - { - var parser = _parser; - Debug.Assert(parser == null || parser.State != TdsParserState.OpenNotLoggedIn, "Reader on a connection that is not logged in"); - if ((parser != null) && (parser.State == TdsParserState.OpenLoggedIn)) - { - // Connection is ok - proper cleanup - // NOTE: This is NOT thread-safe - Close(); - } - else - { - // Connection is broken - quick cleanup - // NOTE: This MUST be thread-safe as a broken connection can happen at any time - - var stateObj = _stateObj; - _isClosed = true; - // Request that the current task is stopped - _cancelAsyncOnCloseTokenSource.Cancel(); - if (stateObj != null) - { - var networkPacketTaskSource = stateObj._networkPacketTaskSource; - if (networkPacketTaskSource != null) - { - // If the connection is closed or broken, this will never complete - networkPacketTaskSource.TrySetException(ADP.ClosedConnectionError()); - } - if (_snapshot != null) - { - // CleanWire will do cleanup - so we don't really care about the snapshot - CleanupAfterAsyncInvocationInternal(stateObj, resetNetworkPacketTaskSource: false); - } - // Switch to sync to prepare for cleanwire - stateObj._syncOverAsync = true; - // Remove owner (this will allow the stateObj to be disposed after the connection is closed) - stateObj.RemoveOwner(); - } - } - } - - private TdsOperationStatus TryConsumeMetaData() - { - // warning: Don't check the MetaData property within this function - // warning: as it will be a reentrant call - while (_parser != null && _stateObj != null && _stateObj.HasPendingData && !_metaDataConsumed) - { - if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) - { - // Happened for DEVDIV2:180509 (SqlDataReader.ConsumeMetaData Hangs In 100% CPU Loop Forever When TdsParser._state == TdsParserState.Broken) - // during request for DTC address. - // NOTE: We doom connection for TdsParserState.Closed since it indicates that it is in some abnormal and unstable state, probably as a result of - // closing from another thread. In general, TdsParserState.Closed does not necessitate dooming the connection. - if (_parser.Connection != null) - { - _parser.Connection.DoomThisConnection(); - } - throw SQL.ConnectionDoomed(); - } - bool ignored; - TdsOperationStatus result = _parser.TryRun(RunBehavior.ReturnImmediately, _command, this, null, _stateObj, out ignored); - if (result != TdsOperationStatus.Done) - { - return result; - } - Debug.Assert(!ignored, "Parser read a row token while trying to read metadata"); - } - - return TdsOperationStatus.Done; - } - - /// - override public string GetDataTypeName(int i) - { - SqlStatistics statistics = null; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - CheckMetaDataIsReady(columnIndex: i); - - return GetDataTypeNameInternal(_metaData[i]); - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - - private string GetDataTypeNameInternal(_SqlMetaData metaData) - { - string dataTypeName = null; - - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) - { - dataTypeName = MetaType.MetaNVarChar.TypeName; - } - else if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) - { - if (_typeSystem == SqlConnectionString.TypeSystem.SQLServer2005) - { - dataTypeName = MetaType.MetaMaxVarBinary.TypeName; - } - else - { - // TypeSystem.SQLServer2000 - dataTypeName = MetaType.MetaImage.TypeName; - } - } - else if (_typeSystem != SqlConnectionString.TypeSystem.SQLServer2000) - { - // TypeSystem.SQLServer2005 and above - - if (metaData.type == SqlDbType.Udt) - { - Debug.Assert(Connection.Is2005OrNewer, "Invalid Column type received from the server"); - dataTypeName = metaData.udt?.DatabaseName + "." + metaData.udt?.SchemaName + "." + metaData.udt?.TypeName; - } - else - { // For all other types, including Xml - use data in MetaType. - if (metaData.cipherMD != null) - { - Debug.Assert(metaData.baseTI != null && metaData.baseTI.metaType != null, "metaData.baseTI and metaData.baseTI.metaType should not be null."); - dataTypeName = metaData.baseTI.metaType.TypeName; - } - else - { - dataTypeName = metaData.metaType.TypeName; - } - } - } - else - { - // TypeSystem.SQLServer2000 - - dataTypeName = GetVersionedMetaType(metaData.metaType).TypeName; - } - - return dataTypeName; - } - - virtual internal SqlBuffer.StorageType GetVariantInternalStorageType(int i) - { - Debug.Assert(_data != null, "Attempting to get variant internal storage type"); - Debug.Assert(i < _data.Length, "Reading beyond data length?"); - - return _data[i].VariantInternalStorageType; - } - - /// - override public IEnumerator GetEnumerator() - { - return new DbEnumerator(this, IsCommandBehavior(CommandBehavior.CloseConnection)); - } - - /// -#if !NETFRAMEWORK - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] -#endif - override public Type GetFieldType(int i) - { - SqlStatistics statistics = null; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - CheckMetaDataIsReady(columnIndex: i); - - return GetFieldTypeInternal(_metaData[i]); - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - -#if !NETFRAMEWORK - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] -#endif - private Type GetFieldTypeInternal(_SqlMetaData metaData) - { - Type fieldType = null; - - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) - { - // Return 2008 types as string - fieldType = MetaType.MetaNVarChar.ClassType; - } - else if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) - { - if (_typeSystem == SqlConnectionString.TypeSystem.SQLServer2005) - { - fieldType = MetaType.MetaMaxVarBinary.ClassType; - } - else - { - // TypeSystem.SQLServer2000 - fieldType = MetaType.MetaImage.ClassType; - } - } - else if (_typeSystem != SqlConnectionString.TypeSystem.SQLServer2000) - { - // TypeSystem.SQLServer2005 and above - if (metaData.type == SqlDbType.Udt) - { - Debug.Assert(Connection.Is2005OrNewer, "Invalid Column type received from the server"); - Connection.CheckGetExtendedUDTInfo(metaData, false); - fieldType = metaData.udt?.Type; - } - else - { // For all other types, including Xml - use data in MetaType. - if (metaData.cipherMD != null) - { - Debug.Assert(metaData.baseTI != null && metaData.baseTI.metaType != null, "metaData.baseTI and metaData.baseTI.metaType should not be null."); - fieldType = metaData.baseTI.metaType.ClassType; - } - else - { - fieldType = metaData.metaType.ClassType; // Com+ type. - } - } - } - else - { - // TypeSystem.SQLServer2000 - fieldType = GetVersionedMetaType(metaData.metaType).ClassType; // Com+ type. - } - - return fieldType; - } - - virtual internal int GetLocaleId(int i) - { - _SqlMetaData sqlMetaData = MetaData[i]; - int lcid; - - if (sqlMetaData.cipherMD != null) - { - // If this column is encrypted, get the collation from baseTI - // - if (sqlMetaData.baseTI.collation != null) - { - lcid = sqlMetaData.baseTI.collation.LCID; - } - else - { - lcid = 0; - } - } - else - { - if (sqlMetaData.collation != null) - { - lcid = sqlMetaData.collation.LCID; - } - else - { - lcid = 0; - } - } - - return lcid; - } - - /// - override public string GetName(int i) - { - CheckMetaDataIsReady(columnIndex: i); - - Debug.Assert(_metaData[i].column != null, "MDAC 66681"); - return _metaData[i].column; - } - - /// - override public Type GetProviderSpecificFieldType(int i) - { - SqlStatistics statistics = null; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - CheckMetaDataIsReady(columnIndex: i); - - return GetProviderSpecificFieldTypeInternal(_metaData[i]); - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - -#if !NETFRAMEWORK - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] -#endif - private Type GetProviderSpecificFieldTypeInternal(_SqlMetaData metaData) - { - Type providerSpecificFieldType = null; - - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) - { - providerSpecificFieldType = MetaType.MetaNVarChar.SqlType; - } - else if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) - { - if (_typeSystem == SqlConnectionString.TypeSystem.SQLServer2005) - { - providerSpecificFieldType = MetaType.MetaMaxVarBinary.SqlType; - } - else - { - // TypeSystem.SQLServer2000 - providerSpecificFieldType = MetaType.MetaImage.SqlType; - } - } - else if (_typeSystem != SqlConnectionString.TypeSystem.SQLServer2000) - { - // TypeSystem.SQLServer2005 and above - if (metaData.type == SqlDbType.Udt) - { - Debug.Assert(Connection.Is2005OrNewer, "Invalid Column type received from the server"); - Connection.CheckGetExtendedUDTInfo(metaData, false); - providerSpecificFieldType = metaData.udt?.Type; - } - else - { - // For all other types, including Xml - use data in MetaType. - if (metaData.cipherMD != null) - { - Debug.Assert(metaData.baseTI != null && metaData.baseTI.metaType != null, - "metaData.baseTI and metaData.baseTI.metaType should not be null."); - providerSpecificFieldType = metaData.baseTI.metaType.SqlType; // SqlType type. - } - else - { - providerSpecificFieldType = metaData.metaType.SqlType; // SqlType type. - } - } - } - else - { - // TypeSystem.SQLServer2000 - providerSpecificFieldType = GetVersionedMetaType(metaData.metaType).SqlType; // SqlType type. - } - - return providerSpecificFieldType; - } - - /// - // named field access - override public int GetOrdinal(string name) - { - SqlStatistics statistics = null; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - if (_fieldNameLookup == null) - { - CheckMetaDataIsReady(); - _fieldNameLookup = new FieldNameLookup(this, _defaultLCID); - } - return _fieldNameLookup.GetOrdinal(name); - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - - /// - override public object GetProviderSpecificValue(int i) - { - return GetSqlValue(i); - } - - /// - override public int GetProviderSpecificValues(object[] values) - { - return GetSqlValues(values); - } - - /// - public override DataTable GetSchemaTable() - { - SqlStatistics statistics = null; - using (TryEventScope.Create(" {0}", ObjectID)) - { - try - { - statistics = SqlStatistics.StartTimer(Statistics); - if (_metaData == null || _metaData.schemaTable == null) - { - if (this.MetaData != null) - { - _metaData.schemaTable = BuildSchemaTable(); - Debug.Assert(_metaData.schemaTable != null, "No schema information yet!"); - } - } - return _metaData?.schemaTable; - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - } - - /// - override public bool GetBoolean(int i) - { - ReadColumn(i); - return _data[i].Boolean; - } - - /// - virtual public XmlReader GetXmlReader(int i) - { - // NOTE: sql_variant can not contain a XML data type: http://msdn.microsoft.com/en-us/library/ms173829.aspx - // If this ever changes, the following code should be changed to be like GetStream/GetTextReader - CheckDataIsReady(columnIndex: i); - - MetaType mt = _metaData[i].metaType; - - // XmlReader only allowed on XML types - if (mt.SqlDbType != SqlDbType.Xml) - { - throw SQL.XmlReaderNotSupportOnColumnType(_metaData[i].column); - } - - if (IsCommandBehavior(CommandBehavior.SequentialAccess)) - { - // Wrap the sequential stream in an XmlReader - _currentStream = new SqlSequentialStream(this, i); - _lastColumnWithDataChunkRead = i; - return SqlTypeWorkarounds.SqlXmlCreateSqlXmlReader(_currentStream, closeInput: true, async: false); - } - else - { - // Need to call ReadColumn, since we want to access the internal data structures (i.e. SqlBinary) rather than calling anther Get*() method - ReadColumn(i); - - if (_data[i].IsNull) - { - // A 'null' stream - return SqlTypeWorkarounds.SqlXmlCreateSqlXmlReader(new MemoryStream(Array.Empty(), writable: false), closeInput: true, async: false); - } - else - { - // Grab already read data - return _data[i].SqlXml.CreateReader(); - } - } - } - - /// - override public Stream GetStream(int i) - { - CheckDataIsReady(columnIndex: i); - - // Streaming is not supported on encrypted columns. - if (_metaData[i] != null && _metaData[i].cipherMD != null) - { - throw SQL.StreamNotSupportOnEncryptedColumn(_metaData[i].column); - } - - // Stream is only for Binary, Image, VarBinary, Udt and Xml types - // NOTE: IsBinType also includes Timestamp for some reason... - MetaType mt = _metaData[i].metaType; - if (((!mt.IsBinType) || (mt.SqlDbType == SqlDbType.Timestamp)) && (mt.SqlDbType != SqlDbType.Variant)) - { - throw SQL.StreamNotSupportOnColumnType(_metaData[i].column); - } - - // For non-variant types with sequential access, we support proper streaming - if ((mt.SqlDbType != SqlDbType.Variant) && (IsCommandBehavior(CommandBehavior.SequentialAccess))) - { - _currentStream = new SqlSequentialStream(this, i); - _lastColumnWithDataChunkRead = i; - return _currentStream; - } - else - { - // Need to call ReadColumn, since we want to access the internal data structures (i.e. SqlBinary) rather than calling anther Get*() method - ReadColumn(i); - - byte[] data; - if (_data[i].IsNull) - { - // A 'null' stream - data = Array.Empty(); - } - else - { - // Grab already read data - data = _data[i].SqlBinary.Value; - } - - // If non-sequential then we just have a read-only MemoryStream - return new MemoryStream(data, writable: false); - } - } - - /// - override public byte GetByte(int i) - { - ReadColumn(i); - return _data[i].Byte; - } - - /// - override public long GetBytes(int i, long dataIndex, byte[] buffer, int bufferIndex, int length) - { - SqlStatistics statistics = null; - long cbBytes = 0; - - CheckDataIsReady(columnIndex: i, allowPartiallyReadColumn: true); - - // don't allow get bytes on non-long or non-binary columns - MetaType mt = _metaData[i].metaType; - if (!(mt.IsLong || mt.IsBinType) || (SqlDbType.Xml == mt.SqlDbType)) - { - throw SQL.NonBlobColumn(_metaData[i].column); - } - - try - { - statistics = SqlStatistics.StartTimer(Statistics); - SetTimeout(_defaultTimeoutMilliseconds); - cbBytes = GetBytesInternal(i, dataIndex, buffer, bufferIndex, length); - _lastColumnWithDataChunkRead = i; - } - finally - { - SqlStatistics.StopTimer(statistics); - } - return cbBytes; - } - - // Used (indirectly) by SqlCommand.CompleteXmlReader - virtual internal long GetBytesInternal(int i, long dataIndex, byte[] buffer, int bufferIndex, int length) - { - if (_currentTask != null) - { - throw ADP.AsyncOperationPending(); - } - - long value; - Debug.Assert(_stateObj == null || _stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - TdsOperationStatus result = TryGetBytesInternal(i, dataIndex, buffer, bufferIndex, length, out value); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - return value; - } - - private TdsOperationStatus TryGetBytesInternal(int i, long dataIndex, byte[] buffer, int bufferIndex, int length, out long remaining) - { - remaining = 0; - TdsOperationStatus result; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - int cbytes = 0; - AssertReaderState(requireData: true, permitAsync: true, columnIndex: i, enforceSequentialAccess: true); - - // sequential reading - if (IsCommandBehavior(CommandBehavior.SequentialAccess)) - { - Debug.Assert(!HasActiveStreamOrTextReaderOnColumn(i), "Column has an active Stream or TextReader"); - - if (_metaData[i] != null && _metaData[i].cipherMD != null) - { - throw SQL.SequentialAccessNotSupportedOnEncryptedColumn(_metaData[i].column); - } - - if (_sharedState._nextColumnHeaderToRead <= i) - { - result = TryReadColumnHeader(i); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - - // If data is null, ReadColumnHeader sets the data.IsNull bit. - if (_data[i] != null && _data[i].IsNull) - { - throw new SqlNullValueException(); - } - - // If there are an unknown (-1) number of bytes left for a PLP, read its size - if ((-1 == _sharedState._columnDataBytesRemaining) && (_metaData[i].metaType.IsPlp)) - { - ulong left; - result = _parser.TryPlpBytesLeft(_stateObj, out left); - if (result != TdsOperationStatus.Done) - { - return result; - } - _sharedState._columnDataBytesRemaining = (long)left; - } - - if (0 == _sharedState._columnDataBytesRemaining) - { - return TdsOperationStatus.Done; // We've read this column to the end - } - - // if no buffer is passed in, return the number total of bytes, or -1 - if (buffer == null) - { - if (_metaData[i].metaType.IsPlp) - { - remaining = (long)_parser.PlpBytesTotalLength(_stateObj); - return TdsOperationStatus.Done; - } - remaining = _sharedState._columnDataBytesRemaining; - return TdsOperationStatus.Done; - } - - if (dataIndex < 0) - { - throw ADP.NegativeParameter(nameof(dataIndex)); - } - - if (dataIndex < _columnDataBytesRead) - { - throw ADP.NonSeqByteAccess(dataIndex, _columnDataBytesRead, nameof(GetBytes)); - } - - // if the dataIndex is not equal to bytes read, then we have to skip bytes - long cb = dataIndex - _columnDataBytesRead; - - // if dataIndex is outside of the data range, return 0 - if ((cb > _sharedState._columnDataBytesRemaining) && !_metaData[i].metaType.IsPlp) - { - return TdsOperationStatus.Done; - } - - // if bad buffer index, throw - if (bufferIndex < 0 || bufferIndex >= buffer.Length) - { - throw ADP.InvalidDestinationBufferIndex(buffer.Length, bufferIndex, nameof(bufferIndex)); - } - - // if there is not enough room in the buffer for data - if (length + bufferIndex > buffer.Length) - { - throw ADP.InvalidBufferSizeOrIndex(length, bufferIndex); - } - - if (length < 0) - { - throw ADP.InvalidDataLength(length); - } - - // Skip if needed - if (cb > 0) - { - if (_metaData[i].metaType.IsPlp) - { - ulong skipped; - result = _parser.TrySkipPlpValue((ulong)cb, _stateObj, out skipped); - if (result != TdsOperationStatus.Done) - { - return result; - } - _columnDataBytesRead += (long)skipped; - } - else - { - result = _stateObj.TrySkipLongBytes(cb); - if (result != TdsOperationStatus.Done) - { - return result; - } - _columnDataBytesRead += cb; - _sharedState._columnDataBytesRemaining -= cb; - } - } - - int bytesRead; - result = TryGetBytesInternalSequential(i, buffer, bufferIndex, length, out bytesRead); - remaining = (int)bytesRead; - return result; - } - - // random access now! - // note that since we are caching in an array, and arrays aren't 64 bit ready yet, - // we need can cast to int if the dataIndex is in range - if (dataIndex < 0) - { - throw ADP.NegativeParameter(nameof(dataIndex)); - } - - if (dataIndex > int.MaxValue) - { - throw ADP.InvalidSourceBufferIndex(cbytes, dataIndex, nameof(dataIndex)); - } - - int ndataIndex = (int)dataIndex; - byte[] data; - - // WebData 99342 - in the non-sequential case, we need to support - // the use of GetBytes on string data columns, but - // GetSqlBinary isn't supposed to. What we end up - // doing isn't exactly pretty, but it does work. - if (_metaData[i].metaType.IsBinType) - { - data = GetSqlBinary(i).Value; - } - else - { - Debug.Assert(_metaData[i].metaType.IsLong, "non long type?"); - Debug.Assert(_metaData[i].metaType.IsCharType, "non-char type?"); - - SqlString temp = GetSqlString(i); - if (_metaData[i].metaType.IsNCharType) - { - data = temp.GetUnicodeBytes(); - } - else - { - data = temp.GetNonUnicodeBytes(); - } - } - - cbytes = data.Length; - - // if no buffer is passed in, return the number of characters we have - if (buffer == null) - { - remaining = cbytes; - return TdsOperationStatus.Done; - } - - // if dataIndex is outside of data range, return 0 - if (ndataIndex < 0 || ndataIndex >= cbytes) - { - return TdsOperationStatus.Done; - } - try - { - if (ndataIndex < cbytes) - { - // help the user out in the case where there's less data than requested - if ((ndataIndex + length) > cbytes) - { - cbytes = cbytes - ndataIndex; - } - else - { - cbytes = length; - } - } - - Buffer.BlockCopy(data, ndataIndex, buffer, bufferIndex, cbytes); - } - catch (Exception e) - { - // UNDONE - should not be catching all exceptions!!! - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - cbytes = data.Length; - - if (length < 0) - { - throw ADP.InvalidDataLength(length); - } - - // if bad buffer index, throw - if (bufferIndex < 0 || bufferIndex >= buffer.Length) - { - throw ADP.InvalidDestinationBufferIndex(buffer.Length, bufferIndex, nameof(bufferIndex)); - } - - // if there is not enough room in the buffer for data - if (cbytes + bufferIndex > buffer.Length) - { - throw ADP.InvalidBufferSizeOrIndex(cbytes, bufferIndex); - } - - throw; - } - - remaining = cbytes; - return TdsOperationStatus.Done; - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.StackOverflowException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.Threading.ThreadAbortException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - } - - internal int GetBytesInternalSequential(int i, byte[] buffer, int index, int length, long? timeoutMilliseconds = null) - { - if (_currentTask != null) - { - throw ADP.AsyncOperationPending(); - } - - int value; - SqlStatistics statistics = null; - Debug.Assert(_stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - try - { - statistics = SqlStatistics.StartTimer(Statistics); - SetTimeout(timeoutMilliseconds ?? _defaultTimeoutMilliseconds); - - TdsOperationStatus result = TryReadColumnHeader(i); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - - result = TryGetBytesInternalSequential(i, buffer, index, length, out value); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - } - finally - { - SqlStatistics.StopTimer(statistics); - } - - return value; - } - - // This is meant to be called from other internal methods once we are at the column to read - // NOTE: This method must be retriable WITHOUT replaying a snapshot - // Every time you call this method increment the index and decrease length by the value of bytesRead - internal TdsOperationStatus TryGetBytesInternalSequential(int i, byte[] buffer, int index, int length, out int bytesRead) - { - AssertReaderState(requireData: true, permitAsync: true, columnIndex: i, enforceSequentialAccess: true); - Debug.Assert(_sharedState._nextColumnHeaderToRead == i + 1 && _sharedState._nextColumnDataToRead == i, "Non sequential access"); - Debug.Assert(buffer != null, "Null buffer"); - Debug.Assert(index >= 0, "Invalid index"); - Debug.Assert(length >= 0, "Invalid length"); - Debug.Assert(index + length <= buffer.Length, "Buffer too small"); - - bytesRead = 0; - TdsOperationStatus result; - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#endif //DEBUG - if ((_sharedState._columnDataBytesRemaining == 0) || (length == 0)) - { - // No data left or nothing requested, return 0 - bytesRead = 0; - return TdsOperationStatus.Done; - } - else - { - // if plp columns, do partial reads. Don't read the entire value in one shot. - if (_metaData[i].metaType.IsPlp) - { - // Read in data - result = _stateObj.TryReadPlpBytes(ref buffer, index, length, out bytesRead); - _columnDataBytesRead += bytesRead; - if (result != TdsOperationStatus.Done) - { - return result; - } - - // Query for number of bytes left - ulong left; - result = _parser.TryPlpBytesLeft(_stateObj, out left); - if (result != TdsOperationStatus.Done) - { - _sharedState._columnDataBytesRemaining = -1; - return result; - } - _sharedState._columnDataBytesRemaining = (long)left; - return TdsOperationStatus.Done; - } - else - { - // Read data (not exceeding the total amount of data available) - int bytesToRead = (int)Math.Min((long)length, _sharedState._columnDataBytesRemaining); - result = _stateObj.TryReadByteArray(buffer.AsSpan(index), bytesToRead, out bytesRead); - _columnDataBytesRead += bytesRead; - _sharedState._columnDataBytesRemaining -= bytesRead; - return result; - } - } -#if DEBUG - } - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.StackOverflowException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.Threading.ThreadAbortException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - } - - /// - override public TextReader GetTextReader(int i) - { - CheckDataIsReady(columnIndex: i); - - // Xml type is not supported - MetaType mt = null; - - if (_metaData[i].cipherMD != null) - { - Debug.Assert(_metaData[i].baseTI != null, "_metaData[i].baseTI should not be null."); - mt = _metaData[i].baseTI.metaType; - } - else - { - mt = _metaData[i].metaType; - } - - Debug.Assert(mt != null, @"mt should not be null."); - - if (((!mt.IsCharType) && (mt.SqlDbType != SqlDbType.Variant)) || (mt.SqlDbType == SqlDbType.Xml)) - { - throw SQL.TextReaderNotSupportOnColumnType(_metaData[i].column); - } - - // For non-variant types with sequential access, we support proper streaming - if ((mt.SqlDbType != SqlDbType.Variant) && (IsCommandBehavior(CommandBehavior.SequentialAccess))) - { - if (_metaData[i].cipherMD != null) - { - throw SQL.SequentialAccessNotSupportedOnEncryptedColumn(_metaData[i].column); - } - - System.Text.Encoding encoding; - if (mt.SqlDbType == SqlDbTypeExtensions.Json) - { - encoding = new UTF8Encoding(); - } - else if (mt.IsNCharType) - { - // NChar types always use unicode - encoding = SqlUnicodeEncoding.SqlUnicodeEncodingInstance; - } - else - { - encoding = _metaData[i].encoding; - } - _currentTextReader = new SqlSequentialTextReader(this, i, encoding); - _lastColumnWithDataChunkRead = i; - return _currentTextReader; - } - else - { - // Need to call ReadColumn, since we want to access the internal data structures (i.e. SqlString) rather than calling anther Get*() method - ReadColumn(i); - - string data; - if (_data[i].IsNull) - { - // A 'null' stream - data = string.Empty; - } - else - { - // Grab already read data - data = _data[i].SqlString.Value; - } - - // We've already read the data, so just wrap it in a StringReader - return new StringReader(data); - } - } - - /// - [EditorBrowsableAttribute(EditorBrowsableState.Never)] - override public char GetChar(int i) - { - throw ADP.NotSupported(); - } - - /// - override public long GetChars(int i, long dataIndex, char[] buffer, int bufferIndex, int length) - { - SqlStatistics statistics = null; - - CheckMetaDataIsReady(columnIndex: i); - - if (_currentTask != null) - { - throw ADP.AsyncOperationPending(); - } - - MetaType mt = null; - if (_metaData[i].cipherMD != null) - { - Debug.Assert(_metaData[i].baseTI != null, @"_metaData[i].baseTI should not be null."); - mt = _metaData[i].baseTI.metaType; - } - else - { - mt = _metaData[i].metaType; - } - - Debug.Assert(mt != null, "mt should not be null."); - - SqlDbType sqlDbType; - if (_metaData[i].cipherMD != null) - { - Debug.Assert(_metaData[i].baseTI != null, @"_metaData[i].baseTI should not be null."); - sqlDbType = _metaData[i].baseTI.type; - } - else - { - sqlDbType = _metaData[i].type; - } - - try - { - statistics = SqlStatistics.StartTimer(Statistics); - SetTimeout(_defaultTimeoutMilliseconds); - if ((mt.IsPlp) && - (IsCommandBehavior(CommandBehavior.SequentialAccess))) - { - if (length < 0) - { - throw ADP.InvalidDataLength(length); - } - - if (_metaData[i].cipherMD != null) - { - throw SQL.SequentialAccessNotSupportedOnEncryptedColumn(_metaData[i].column); - } - - // if bad buffer index, throw - if ((bufferIndex < 0) || (buffer != null && bufferIndex >= buffer.Length)) - { - throw ADP.InvalidDestinationBufferIndex(buffer.Length, bufferIndex, nameof(bufferIndex)); - } - - // if there is not enough room in the buffer for data - if (buffer != null && (length + bufferIndex > buffer.Length)) - { - throw ADP.InvalidBufferSizeOrIndex(length, bufferIndex); - } - long charsRead = 0; - if (sqlDbType == SqlDbType.Xml) - { - try - { - CheckDataIsReady(columnIndex: i, allowPartiallyReadColumn: true); - } - catch (Exception ex) - { - // We need to wrap all exceptions inside a TargetInvocationException to simulate calling CreateSqlReader via MethodInfo.Invoke - if (ADP.IsCatchableExceptionType(ex)) - { - throw new TargetInvocationException(ex); - } - else - { - throw; - } - } - charsRead = GetStreamingXmlChars(i, dataIndex, buffer, bufferIndex, length); - } - else - { - CheckDataIsReady(columnIndex: i, allowPartiallyReadColumn: true); - charsRead = GetCharsFromPlpData(i, dataIndex, buffer, bufferIndex, length); - } - _lastColumnWithDataChunkRead = i; - return charsRead; - } - - // Did we start reading this value yet? - if ((_sharedState._nextColumnDataToRead == (i + 1)) && (_sharedState._nextColumnHeaderToRead == (i + 1)) && (_columnDataChars != null) && (IsCommandBehavior(CommandBehavior.SequentialAccess)) && (dataIndex < _columnDataCharsRead)) - { - // Don't allow re-read of same chars in sequential access mode - throw ADP.NonSeqByteAccess(dataIndex, _columnDataCharsRead, nameof(GetChars)); - } - - if (_columnDataCharsIndex != i) - { - // if the object doesn't contain a char[] then the user will get an exception - string s = GetSqlString(i).Value; - - _columnDataChars = s.ToCharArray(); - _columnDataCharsRead = 0; - _columnDataCharsIndex = i; - } - - int cchars = _columnDataChars.Length; - - // note that since we are caching in an array, and arrays aren't 64 bit ready yet, - // we need can cast to int if the dataIndex is in range - if (dataIndex > int.MaxValue) - { - throw ADP.InvalidSourceBufferIndex(cchars, dataIndex, nameof(dataIndex)); - } - int ndataIndex = (int)dataIndex; - - // if no buffer is passed in, return the number of characters we have - if (buffer == null) - { - return cchars; - } - - // if dataIndex outside of data range, return 0 - if (ndataIndex < 0 || ndataIndex >= cchars) - { - return 0; - } - - try - { - if (ndataIndex < cchars) - { - // help the user out in the case where there's less data than requested - if ((ndataIndex + length) > cchars) - { - cchars = cchars - ndataIndex; - } - else - { - cchars = length; - } - } - - Array.Copy(_columnDataChars, ndataIndex, buffer, bufferIndex, cchars); - _columnDataCharsRead += cchars; - } - catch (Exception e) - { - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } - cchars = _columnDataChars.Length; - - if (length < 0) - { - throw ADP.InvalidDataLength(length); - } - - // if bad buffer index, throw - if (bufferIndex < 0 || bufferIndex >= buffer.Length) - { - throw ADP.InvalidDestinationBufferIndex(buffer.Length, bufferIndex, nameof(bufferIndex)); - } - - // if there is not enough room in the buffer for data - if (cchars + bufferIndex > buffer.Length) - { - throw ADP.InvalidBufferSizeOrIndex(cchars, bufferIndex); - } - - throw; - } - - return cchars; - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - - private long GetCharsFromPlpData(int i, long dataIndex, char[] buffer, int bufferIndex, int length) - { - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - long cch; - - AssertReaderState(requireData: true, permitAsync: false, columnIndex: i, enforceSequentialAccess: true); - Debug.Assert(!HasActiveStreamOrTextReaderOnColumn(i), "Column has active Stream or TextReader"); - // don't allow get bytes on non-long or non-binary columns - Debug.Assert(_metaData[i].metaType.IsPlp, "GetCharsFromPlpData called on a non-plp column!"); - // Must be sequential reading - Debug.Assert(IsCommandBehavior(CommandBehavior.SequentialAccess), "GetCharsFromPlpData called for non-Sequential access"); - - if (!_metaData[i].metaType.IsCharType) - { - throw SQL.NonCharColumn(_metaData[i].column); - } - - if (_sharedState._nextColumnHeaderToRead <= i) - { - ReadColumnHeader(i); - } - - // If data is null, ReadColumnHeader sets the data.IsNull bit. - if (_data[i] != null && _data[i].IsNull) - { - throw new SqlNullValueException(); - } - - if (dataIndex < _columnDataCharsRead) - { - // Don't allow re-read of same chars in sequential access mode - throw ADP.NonSeqByteAccess(dataIndex, _columnDataCharsRead, nameof(GetChars)); - } - - // If we start reading the new column, either dataIndex is 0 or - // _columnDataCharsRead is 0 and dataIndex > _columnDataCharsRead is true below. - // In both cases we will clean decoder - if (dataIndex == 0) - { - _stateObj._plpdecoder = null; - } - - bool isUnicode = _metaData[i].metaType.IsNCharType; - - // If there are an unknown (-1) number of bytes left for a PLP, read its size - if (-1 == _sharedState._columnDataBytesRemaining) - { - _sharedState._columnDataBytesRemaining = (long)_parser.PlpBytesLeft(_stateObj); - } - - if (0 == _sharedState._columnDataBytesRemaining) - { - _stateObj._plpdecoder = null; - return 0; // We've read this column to the end - } - - // if no buffer is passed in, return the total number of characters or -1 - // TODO: for DBCS encoding it returns number of bytes, not number of chars - if (buffer == null) - { - cch = (long)_parser.PlpBytesTotalLength(_stateObj); - return (isUnicode && (cch > 0)) ? cch >> 1 : cch; - } - if (dataIndex > _columnDataCharsRead) - { - // Skip chars - - // Clean decoder state: we do not reset it, but destroy to ensure - // that we do not start decoding the column with decoder from the old one - _stateObj._plpdecoder = null; - // TODO: for DBCS encoding skip positioning dataIndex is not in characters but is interpreted as - // number of chars already read + number of bytes to skip - cch = dataIndex - _columnDataCharsRead; - cch = isUnicode ? (cch << 1) : cch; - cch = (long)_parser.SkipPlpValue((ulong)(cch), _stateObj); - _columnDataBytesRead += cch; - _columnDataCharsRead += (isUnicode && (cch > 0)) ? cch >> 1 : cch; - } - cch = length; - - if (isUnicode) - { - cch = (long)_parser.ReadPlpUnicodeChars(ref buffer, bufferIndex, length, _stateObj); - _columnDataBytesRead += (cch << 1); - } - else - { - cch = (long)_parser.ReadPlpAnsiChars(ref buffer, bufferIndex, length, _metaData[i], _stateObj); - _columnDataBytesRead += cch << 1; - } - _columnDataCharsRead += cch; - _sharedState._columnDataBytesRemaining = (long)_parser.PlpBytesLeft(_stateObj); - return cch; - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.StackOverflowException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.Threading.ThreadAbortException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - } - - internal long GetStreamingXmlChars(int i, long dataIndex, char[] buffer, int bufferIndex, int length) - { - SqlStreamingXml localSXml = null; - if ((_streamingXml != null) && (_streamingXml.ColumnOrdinal != i)) - { - _streamingXml.Close(); - _streamingXml = null; - } - if (_streamingXml == null) - { - localSXml = new SqlStreamingXml(i, this); - } - else - { - localSXml = _streamingXml; - } - long cnt = localSXml.GetChars(dataIndex, buffer, bufferIndex, length); - if (_streamingXml == null) - { - // Data is read through GetBytesInternal which may dispose _streamingXml if it has to advance the column ordinal. - // Therefore save the new SqlStreamingXml class after the read succeeds. - _streamingXml = localSXml; - } - return cnt; - } - - /// - [EditorBrowsableAttribute(EditorBrowsableState.Never)] // MDAC 69508 - IDataReader IDataRecord.GetData(int i) - { - throw ADP.NotSupported(); - } - - /// - override public DateTime GetDateTime(int i) - { - ReadColumn(i); - - DateTime dt = _data[i].DateTime; - // This accessor can be called for regular DateTime column. In this case we should not throw - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && _metaData[i].Is2008DateTimeType) - { - // TypeSystem.SQLServer2005 or less - - // If the above succeeds, then we received a valid DateTime instance, now we need to force - // an InvalidCastException since DateTime is not exposed with the version knob in this setting. - // To do so, we simply force the exception by casting the string representation of the value - // To DateTime. - object temp = (object)_data[i].String; - dt = (DateTime)temp; - } - - return dt; - } - - /// - override public decimal GetDecimal(int i) - { - ReadColumn(i); - return _data[i].Decimal; - } - - /// - override public double GetDouble(int i) - { - ReadColumn(i); - return _data[i].Double; - } - - /// - override public float GetFloat(int i) - { - ReadColumn(i); - return _data[i].Single; - } - - /// - override public Guid GetGuid(int i) - { - ReadColumn(i); - return _data[i].Guid; - } - - /// - override public short GetInt16(int i) - { - ReadColumn(i); - return _data[i].Int16; - } - - /// - override public int GetInt32(int i) - { - ReadColumn(i); - return _data[i].Int32; - } - - /// - override public long GetInt64(int i) - { - ReadColumn(i); - return _data[i].Int64; - } - - /// - virtual public SqlBoolean GetSqlBoolean(int i) - { - ReadColumn(i); - return _data[i].SqlBoolean; - } - - /// - virtual public SqlBinary GetSqlBinary(int i) - { - ReadColumn(i, setTimeout: true, allowPartiallyReadColumn: true); - return _data[i].SqlBinary; - } - - /// - virtual public SqlByte GetSqlByte(int i) - { - ReadColumn(i); - return _data[i].SqlByte; - } - - /// - virtual public SqlBytes GetSqlBytes(int i) - { - ReadColumn(i); - SqlBinary data = _data[i].SqlBinary; - return new SqlBytes(data); - } - - /// - virtual public SqlChars GetSqlChars(int i) - { - ReadColumn(i); - SqlString data; - // Convert 2008 types to string - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && _metaData[i].Is2008DateTimeType) - { - data = _data[i].Sql2008DateTimeSqlString; - } - else - { - data = _data[i].SqlString; - } - return new SqlChars(data); - } - - /// - virtual public SqlDateTime GetSqlDateTime(int i) - { - ReadColumn(i); - return _data[i].SqlDateTime; - } - - /// - virtual public SqlDecimal GetSqlDecimal(int i) - { - ReadColumn(i); - return _data[i].SqlDecimal; - } - - /// - virtual public SqlGuid GetSqlGuid(int i) - { - ReadColumn(i); - return _data[i].SqlGuid; - } - - /// - virtual public SqlDouble GetSqlDouble(int i) - { - ReadColumn(i); - return _data[i].SqlDouble; - } - - /// - virtual public SqlInt16 GetSqlInt16(int i) - { - ReadColumn(i); - return _data[i].SqlInt16; - } - - /// - virtual public SqlInt32 GetSqlInt32(int i) - { - ReadColumn(i); - return _data[i].SqlInt32; - } - - /// - virtual public SqlInt64 GetSqlInt64(int i) - { - ReadColumn(i); - return _data[i].SqlInt64; - } - - /// - virtual public SqlMoney GetSqlMoney(int i) - { - ReadColumn(i); - return _data[i].SqlMoney; - } - - /// - virtual public SqlSingle GetSqlSingle(int i) - { - ReadColumn(i); - return _data[i].SqlSingle; - } - - /// - virtual public SqlString GetSqlString(int i) - { - ReadColumn(i); - - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && _metaData[i].Is2008DateTimeType) - { - return _data[i].Sql2008DateTimeSqlString; - } - - return _data[i].SqlString; - } - - /// - virtual public SqlXml GetSqlXml(int i) - { - ReadColumn(i); - SqlXml sx = null; - - if (_typeSystem != SqlConnectionString.TypeSystem.SQLServer2000) - { - // TypeSystem.SQLServer2005 - - sx = _data[i].IsNull ? SqlXml.Null : _data[i].SqlCachedBuffer.ToSqlXml(); - } - else - { - // TypeSystem.SQLServer2000 - - // First, attempt to obtain SqlXml value. If not SqlXml, we will throw the appropriate - // cast exception. - sx = _data[i].IsNull ? SqlXml.Null : _data[i].SqlCachedBuffer.ToSqlXml(); - - // If the above succeeds, then we received a valid SqlXml instance, now we need to force - // an InvalidCastException since SqlXml is not exposed with the version knob in this setting. - // To do so, we simply force the exception by casting the string representation of the value - // To SqlXml. - object temp = (object)_data[i].String; - sx = (SqlXml)temp; - } - - return sx; - } - - /// - virtual public SqlJson GetSqlJson(int i) - { - ReadColumn(i); - SqlJson json = _data[i].IsNull ? SqlJson.Null : _data[i].SqlJson; - return json; - } - - /// - virtual public object GetSqlValue(int i) - { - SqlStatistics statistics = null; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - - SetTimeout(_defaultTimeoutMilliseconds); - return GetSqlValueInternal(i); - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - - private object GetSqlValueInternal(int i) - { - if (_currentTask != null) - { - throw ADP.AsyncOperationPending(); - } - - Debug.Assert(_stateObj == null || _stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - TdsOperationStatus result = TryReadColumn(i, setTimeout: false); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - - return GetSqlValueFromSqlBufferInternal(_data[i], _metaData[i]); - } - - // NOTE: This method is called by the fast-paths in Async methods and, therefore, should be resilient to the DataReader being closed - // Always make sure to take reference copies of anything set to null in TryCloseInternal() - private object GetSqlValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData metaData) - { - Debug.Assert(!data.IsEmpty || data.IsNull || metaData.type == SqlDbType.Timestamp, "Data has been read, but the buffer is empty"); - - // Convert 2008 types to string - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) - { - return data.Sql2008DateTimeSqlString; - } - else if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) - { - return data.SqlValue; - } - else if (_typeSystem != SqlConnectionString.TypeSystem.SQLServer2000) - { - // TypeSystem.SQLServer2005 and above - - if (metaData.type == SqlDbType.Udt) - { - SqlConnection connection = _connection; - if (connection != null) - { - connection.CheckGetExtendedUDTInfo(metaData, true); - return connection.GetUdtValue(data.Value, metaData, false); - } - else - { - throw ADP.DataReaderClosed(); - } - } - else - { - return data.SqlValue; - } - } - else - { - // TypeSystem.SQLServer2000 - - if (metaData.type == SqlDbType.Xml) - { - return data.SqlString; - } - else - { - return data.SqlValue; - } - } - } - - /// - virtual public int GetSqlValues(object[] values) - { - SqlStatistics statistics = null; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - CheckDataIsReady(); - if (values == null) - { - throw ADP.ArgumentNull(nameof(values)); - } - - SetTimeout(_defaultTimeoutMilliseconds); - - int copyLen = (values.Length < _metaData.VisibleColumnCount) ? values.Length : _metaData.VisibleColumnCount; - - for (int i = 0; i < copyLen; i++) - { - values[i] = GetSqlValueInternal(_metaData.GetVisibleColumnIndex(i)); - } - return copyLen; - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - - /// - override public string GetString(int i) - { - ReadColumn(i); - - // Convert 2008 value to string if type system knob is 2005 or earlier - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && _metaData[i].Is2008DateTimeType) - { - return _data[i].Sql2008DateTimeString; - } - - return _data[i].String; - } - - /// - override public T GetFieldValue(int i) - { - SqlStatistics statistics = null; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - - SetTimeout(_defaultTimeoutMilliseconds); - return GetFieldValueInternal(i, isAsync: false); - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - - /// - override public object GetValue(int i) - { - SqlStatistics statistics = null; - try - { - statistics = SqlStatistics.StartTimer(Statistics); - - SetTimeout(_defaultTimeoutMilliseconds); - return GetValueInternal(i); - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - - /// - virtual public TimeSpan GetTimeSpan(int i) - { - ReadColumn(i); - - TimeSpan t = _data[i].Time; - - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005) - { - // TypeSystem.SQLServer2005 or less - - // If the above succeeds, then we received a valid TimeSpan instance, now we need to force - // an InvalidCastException since TimeSpan is not exposed with the version knob in this setting. - // To do so, we simply force the exception by casting the string representation of the value - // To TimeSpan. - object temp = (object)_data[i].String; - t = (TimeSpan)temp; - } - - return t; - } - - /// - virtual public DateTimeOffset GetDateTimeOffset(int i) - { - ReadColumn(i); - - DateTimeOffset dto = _data[i].DateTimeOffset; - - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005) - { - // TypeSystem.SQLServer2005 or less - - // If the above succeeds, then we received a valid DateTimeOffset instance, now we need to force - // an InvalidCastException since DateTime is not exposed with the version knob in this setting. - // To do so, we simply force the exception by casting the string representation of the value - // To DateTimeOffset. - object temp = (object)_data[i].String; - dto = (DateTimeOffset)temp; - } - - return dto; - } - - private object GetValueInternal(int i) - { - if (_currentTask != null) - { - throw ADP.AsyncOperationPending(); - } - - Debug.Assert(_stateObj == null || _stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - TdsOperationStatus result = TryReadColumn(i, setTimeout: false); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - - return GetValueFromSqlBufferInternal(_data[i], _metaData[i]); - } - - // NOTE: This method is called by the fast-paths in Async methods and, therefore, should be resilient to the DataReader being closed - // Always make sure to take reference copies of anything set to null in TryCloseInternal() - private object GetValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData metaData) - { - Debug.Assert(!data.IsEmpty || data.IsNull || metaData.type == SqlDbType.Timestamp, "Data has been read, but the buffer is empty"); - - if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) - { - if (data.IsNull) - { - return DBNull.Value; - } - else - { - return data.Sql2008DateTimeString; - } - } - else if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && metaData.IsLargeUdt) - { - return data.Value; - } - else if (_typeSystem != SqlConnectionString.TypeSystem.SQLServer2000) - { - // TypeSystem.SQLServer2005 and above - - if (metaData.type != SqlDbType.Udt) - { - return data.Value; - } - else - { - SqlConnection connection = _connection; - if (connection != null) - { - connection.CheckGetExtendedUDTInfo(metaData, true); - return connection.GetUdtValue(data.Value, metaData, true); - } - else - { - throw ADP.DataReaderClosed(); - } - } - } - else - { - // TypeSystem.SQLServer2000 - return data.Value; - } - } - - private T GetFieldValueInternal(int i, bool isAsync) - { - if (_currentTask != null) - { - throw ADP.AsyncOperationPending(); - } - - Debug.Assert(_stateObj == null || _stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - bool forStreaming = typeof(T) == typeof(XmlReader) || typeof(T) == typeof(TextReader) || typeof(T) == typeof(Stream); - TdsOperationStatus result = TryReadColumn(i, setTimeout: false, forStreaming: forStreaming); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - - return GetFieldValueFromSqlBufferInternal(_data[i], _metaData[i], isAsync: isAsync); - } - - private T GetFieldValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData metaData, bool isAsync) - { - // this block of type specific shortcuts uses RyuJIT jit behaviors to achieve fast implementations of the primitive types - // RyuJIT will be able to determine at compilation time that the typeof(T)==typeof() options are constant - // and be able to remove all implementations which cannot be reached. this will eliminate non-specialized code for - Type dataType = data.GetTypeFromStorageType(false); - if (typeof(T) == typeof(int) && dataType == typeof(int)) - { - return data.Int32As(); - } - else if (typeof(T) == typeof(byte) && dataType == typeof(byte)) - { - return data.ByteAs(); - } - else if (typeof(T) == typeof(short) && dataType == typeof(short)) - { - return data.Int16As(); - } - else if (typeof(T) == typeof(long) && dataType == typeof(long)) - { - return data.Int64As(); - } - else if (typeof(T) == typeof(bool) && dataType == typeof(bool)) - { - return data.BooleanAs(); - } - else if (typeof(T) == typeof(double) && dataType == typeof(double)) - { - return data.DoubleAs(); - } - else if (typeof(T) == typeof(float) && dataType == typeof(float)) - { - return data.SingleAs(); - } - else if (typeof(T) == typeof(Guid) && dataType == typeof(Guid)) - { - return (T)(object)data.Guid; - } - else if (typeof(T) == typeof(decimal) && dataType == typeof(decimal)) - { - return (T)(object)data.Decimal; - } - else if (typeof(T) == typeof(DateTimeOffset) && dataType == typeof(DateTimeOffset) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) - { - return (T)(object)data.DateTimeOffset; - } - else if (typeof(T) == typeof(DateTime) && dataType == typeof(DateTime) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005 && metaData.Is2008DateTimeType) - { - return (T)(object)data.DateTime; - } -#if !NETFRAMEWORK - else if (typeof(T) == typeof(DateOnly) && dataType == typeof(DateTime) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005) - { - return (T)(object)data.DateOnly; - } - else if (typeof(T) == typeof(TimeOnly) && dataType == typeof(TimeOnly) && _typeSystem > SqlConnectionString.TypeSystem.SQLServer2005) - { - return (T)(object)data.TimeOnly; - } -#endif - else if (typeof(T) == typeof(XmlReader)) - { - // XmlReader only allowed on XML types - if (metaData.metaType.SqlDbType != SqlDbType.Xml) - { - throw SQL.XmlReaderNotSupportOnColumnType(metaData.column); - } - - if (IsCommandBehavior(CommandBehavior.SequentialAccess)) - { - // Wrap the sequential stream in an XmlReader - _currentStream = new SqlSequentialStream(this, metaData.ordinal); - _lastColumnWithDataChunkRead = metaData.ordinal; - return (T)(object)SqlTypeWorkarounds.SqlXmlCreateSqlXmlReader(_currentStream, closeInput: true, async: isAsync); - } - else - { - if (data.IsNull) - { - // A 'null' stream - return (T)(object)SqlTypeWorkarounds.SqlXmlCreateSqlXmlReader(new MemoryStream(Array.Empty(), writable: false), closeInput: true, async: isAsync); - } - else - { - // Grab already read data - return (T)(object)data.SqlXml.CreateReader(); - } - } - } - else if (typeof(T) == typeof(TextReader)) - { - // Xml type is not supported - MetaType metaType = metaData.metaType; - if (metaData.cipherMD != null) - { - Debug.Assert(metaData.baseTI != null, "_metaData[i].baseTI should not be null."); - metaType = metaData.baseTI.metaType; - } - - if ( - (!metaType.IsCharType && metaType.SqlDbType != SqlDbType.Variant) || - (metaType.SqlDbType == SqlDbType.Xml) - ) - { - throw SQL.TextReaderNotSupportOnColumnType(metaData.column); - } - - // For non-variant types with sequential access, we support proper streaming - if ((metaType.SqlDbType != SqlDbType.Variant) && IsCommandBehavior(CommandBehavior.SequentialAccess)) - { - if (metaData.cipherMD != null) - { - throw SQL.SequentialAccessNotSupportedOnEncryptedColumn(metaData.column); - } - - System.Text.Encoding encoding = SqlUnicodeEncoding.SqlUnicodeEncodingInstance; - if (!metaType.IsNCharType) - { - encoding = metaData.encoding; - } - - _currentTextReader = new SqlSequentialTextReader(this, metaData.ordinal, encoding); - _lastColumnWithDataChunkRead = metaData.ordinal; - return (T)(object)_currentTextReader; - } - else - { - string value = data.IsNull ? string.Empty : data.SqlString.Value; - return (T)(object)new StringReader(value); - } - - } - else if (typeof(T) == typeof(Stream)) - { - if (metaData != null && metaData.cipherMD != null) - { - throw SQL.StreamNotSupportOnEncryptedColumn(metaData.column); - } - - // Stream is only for Binary, Image, VarBinary, Udt, Xml and Timestamp(RowVersion) types - MetaType metaType = metaData.metaType; - if ( - (!metaType.IsBinType || metaType.SqlDbType == SqlDbType.Timestamp) && - metaType.SqlDbType != SqlDbType.Variant - ) - { - throw SQL.StreamNotSupportOnColumnType(metaData.column); - } - - if ((metaType.SqlDbType != SqlDbType.Variant) && (IsCommandBehavior(CommandBehavior.SequentialAccess))) - { - _currentStream = new SqlSequentialStream(this, metaData.ordinal); - _lastColumnWithDataChunkRead = metaData.ordinal; - return (T)(object)_currentStream; - } - else - { - byte[] value = data.IsNull ? Array.Empty() : data.SqlBinary.Value; - return (T)(object)new MemoryStream(value, writable: false); - } - } - else if (typeof(T) == typeof(JsonDocument)) - { - MetaType metaType = metaData.metaType; - if (metaType.SqlDbType != SqlDbTypeExtensions.Json) - { - throw SQL.JsonDocumentNotSupportedOnColumnType(metaData.column); - } - JsonDocument document = JsonDocument.Parse(data.Value as string); - return (T)(object)document; - } - else - { - if (typeof(INullable).IsAssignableFrom(typeof(T))) - { - // If its a SQL Type or Nullable UDT - object rawValue = GetSqlValueFromSqlBufferInternal(data, metaData); - if (typeof(T) == typeof(SqlString)) - { - // Special case: User wants SqlString, but we have a SqlXml - // SqlXml can not be typecast into a SqlString, but we need to support SqlString on XML Types - so do a manual conversion - SqlXml xmlValue = rawValue as SqlXml; - if (xmlValue != null) - { - if (xmlValue.IsNull) - { - rawValue = SqlString.Null; - } - else - { - rawValue = new SqlString(xmlValue.Value); - } - } - } - return (T)rawValue; - } - else - { - // the requested type is likely to be one that isn't supported so try the cast and - // unless there is a null value conversion then feedback the cast exception with - // type named to the user so they know what went wrong. Supported types are listed - // in the documentation - try - { - return (T)GetValueFromSqlBufferInternal(data, metaData); - } - catch (InvalidCastException) when (data.IsNull) - { - // If the value was actually null, then we should throw a SqlNullValue instead - throw SQL.SqlNullValue(); - } - - } - } - } - - /// - override public int GetValues(object[] values) - { - SqlStatistics statistics = null; - bool sequentialAccess = IsCommandBehavior(CommandBehavior.SequentialAccess); - - try - { - statistics = SqlStatistics.StartTimer(Statistics); - - if (values == null) - { - throw ADP.ArgumentNull(nameof(values)); - } - - CheckMetaDataIsReady(); - - int copyLen = (values.Length < _metaData.VisibleColumnCount) ? values.Length : _metaData.VisibleColumnCount; - int maximumColumn = copyLen - 1; - - SetTimeout(_defaultTimeoutMilliseconds); - - // Temporarily disable sequential access - _commandBehavior &= ~CommandBehavior.SequentialAccess; - - // Read in all of the columns in one TryReadColumn call - TdsOperationStatus result = TryReadColumn(maximumColumn, setTimeout: false); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - - for (int i = 0; i < copyLen; i++) - { - // Get the usable, TypeSystem-compatible value from the internal buffer - int fieldIndex = _metaData.GetVisibleColumnIndex(i); - values[i] = GetValueFromSqlBufferInternal(_data[fieldIndex], _metaData[fieldIndex]); - - // If this is sequential access, then we need to wipe the internal buffer - if ((sequentialAccess) && (i < maximumColumn)) - { - _data[fieldIndex].Clear(); - if (fieldIndex > i && fieldIndex > 0) - { - // if we jumped an index forward because of a hidden column see if the buffer before the - // current one was populated by the seek forward and clear it if it was - _data[fieldIndex - 1].Clear(); - } - } - } - - return copyLen; - } - finally - { - // Restore sequential access - if (sequentialAccess) - { - _commandBehavior |= CommandBehavior.SequentialAccess; - } - - SqlStatistics.StopTimer(statistics); - } - } - - private MetaType GetVersionedMetaType(MetaType actualMetaType) - { - Debug.Assert(_typeSystem == SqlConnectionString.TypeSystem.SQLServer2000, "Should not be in this function under anything else but SQLServer2000"); - - MetaType metaType = null; - - if (actualMetaType == MetaType.MetaUdt) - { - metaType = MetaType.MetaVarBinary; - } - else if (actualMetaType == MetaType.MetaXml) - { - metaType = MetaType.MetaNText; - } - else if (actualMetaType == MetaType.MetaMaxVarBinary) - { - metaType = MetaType.MetaImage; - } - else if (actualMetaType == MetaType.MetaMaxVarChar) - { - metaType = MetaType.MetaText; - } - else if (actualMetaType == MetaType.MetaMaxNVarChar) - { - metaType = MetaType.MetaNText; - } - else - { - metaType = actualMetaType; - } - - return metaType; - } - - private TdsOperationStatus TryHasMoreResults(out bool moreResults) - { - if (_parser != null) - { - bool moreRows; - TdsOperationStatus result = TryHasMoreRows(out moreRows); - if (result != TdsOperationStatus.Done) - { - moreResults = false; - return result; - } - if (moreRows) - { - // When does this happen? This is only called from NextResult(), which loops until Read() false. - moreResults = false; - return TdsOperationStatus.Done; - } - - Debug.Assert(_command != null, "unexpected null command from the data reader!"); - - while (_stateObj.HasPendingData) - { - byte token; - result = _stateObj.TryPeekByte(out token); - if (result != TdsOperationStatus.Done) - { - moreResults = false; - return result; - } - - switch (token) - { - case TdsEnums.SQLROW: - case TdsEnums.SQLNBCROW: - // always happens if there is a row following an altrow - moreResults = true; - return TdsOperationStatus.Done; - case TdsEnums.SQLDONE: - Debug.Assert(_altRowStatus == ALTROWSTATUS.Done || _altRowStatus == ALTROWSTATUS.Null, "invalid AltRowStatus"); - _altRowStatus = ALTROWSTATUS.Null; - _metaData = null; - _altMetaDataSetCollection = null; - moreResults = true; - return TdsOperationStatus.Done; - case TdsEnums.SQLCOLMETADATA: - moreResults = true; - return TdsOperationStatus.Done; - - // deprecated - case TdsEnums.SQLALTROW: - if (_altRowStatus == ALTROWSTATUS.Null) - { - // cache the regular metadata - _altMetaDataSetCollection.metaDataSet = _metaData; - _metaData = null; - } - else - { - Debug.Assert(_altRowStatus == ALTROWSTATUS.Done, "invalid AltRowStatus"); - } - _altRowStatus = ALTROWSTATUS.AltRow; - _hasRows = true; - moreResults = true; - return TdsOperationStatus.Done; - } - - // TryRun() will immediately return if the TdsParser is closed/broken, causing us to enter an infinite loop - // Instead, we will throw a closed connection exception - if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) - { - throw ADP.ClosedConnectionError(); - } - - result = _parser.TryRun(RunBehavior.ReturnImmediately, _command, this, null, _stateObj, out _); - if (result != TdsOperationStatus.Done) - { - moreResults = false; - return result; - } - } - } - moreResults = false; - return TdsOperationStatus.Done; - } - - private TdsOperationStatus TryHasMoreRows(out bool moreRows) - { - if (_parser != null) - { - if (_sharedState._dataReady) - { - moreRows = true; - return TdsOperationStatus.Done; - } - - // NextResult: previous call to NextResult started to process the altrowpackage, can't peek anymore - // Read: Read prepared for final processing of altrow package, No more Rows until NextResult ... - // Done: Done processing the altrow, no more rows until NextResult ... - switch (_altRowStatus) - { - case ALTROWSTATUS.AltRow: - moreRows = true; - return TdsOperationStatus.Done; - case ALTROWSTATUS.Done: - moreRows = false; - return TdsOperationStatus.Done; - } - if (_stateObj.HasPendingData) - { - // Consume error's, info's, done's on HasMoreRows, so user obtains error on Read. - byte b; - TdsOperationStatus result = _stateObj.TryPeekByte(out b); - if (result != TdsOperationStatus.Done) - { - moreRows = false; - return result; - } - bool ParsedDoneToken = false; - - while (b == TdsEnums.SQLDONE || - b == TdsEnums.SQLDONEPROC || - b == TdsEnums.SQLDONEINPROC || - !ParsedDoneToken && ( - b == TdsEnums.SQLSESSIONSTATE || - b == TdsEnums.SQLENVCHANGE || - b == TdsEnums.SQLORDER || - b == TdsEnums.SQLERROR || - b == TdsEnums.SQLINFO)) - { - if (b == TdsEnums.SQLDONE || - b == TdsEnums.SQLDONEPROC || - b == TdsEnums.SQLDONEINPROC) - { - ParsedDoneToken = true; - } - - // TryRun() will immediately return if the TdsParser is closed/broken, causing us to enter an infinite loop - // Instead, we will throw a closed connection exception - if (_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed) - { - throw ADP.ClosedConnectionError(); - } - - result = _parser.TryRun(RunBehavior.ReturnImmediately, _command, this, null, _stateObj, out _); - if (result != TdsOperationStatus.Done) - { - moreRows = false; - return result; - } - if (_stateObj.HasPendingData) - { - result = _stateObj.TryPeekByte(out b); - if (result != TdsOperationStatus.Done) - { - moreRows = false; - return result; - } - } - else - { - break; - } - } - - // Only return true when we are positioned on a row token. - if (IsRowToken(b)) - { - moreRows = true; - return TdsOperationStatus.Done; - } - } - } - moreRows = false; - return TdsOperationStatus.Done; - } - - private bool IsRowToken(byte token) - { - return TdsEnums.SQLROW == token || TdsEnums.SQLNBCROW == token; - } - - /// - override public bool IsDBNull(int i) - { - if ((IsCommandBehavior(CommandBehavior.SequentialAccess)) && ((_sharedState._nextColumnHeaderToRead > i + 1) || (_lastColumnWithDataChunkRead > i))) - { - // Bug 447026 : A breaking change in System.Data .NET 4.5 for calling IsDBNull on commands in SequentialAccess mode - // http://vstfdevdiv:8080/web/wi.aspx?pcguid=22f9acc9-569a-41ff-b6ac-fac1b6370209&id=447026 - // In .NET 4.0 and previous, it was possible to read a previous column using IsDBNull when in sequential mode - // However, since it had already gone past the column, the current IsNull value is simply returned - - // To replicate this behavior we will skip CheckHeaderIsReady\ReadColumnHeader and instead just check that the reader is ready and the column is valid - CheckMetaDataIsReady(columnIndex: i); - } - else - { - CheckHeaderIsReady(columnIndex: i); - - SetTimeout(_defaultTimeoutMilliseconds); - - ReadColumnHeader(i); // header data only - } - - return _data[i].IsNull; - } - - /// - protected internal bool IsCommandBehavior(CommandBehavior condition) - { - return (condition == (condition & _commandBehavior)); - } - - /// - override public bool NextResult() - { - if (_currentTask != null) - { - throw SQL.PendingBeginXXXExists(); - } - - bool more; - - Debug.Assert(_stateObj == null || _stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - TdsOperationStatus result = TryNextResult(out more); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - return more; - } - - // recordset is automatically positioned on the first result set - private TdsOperationStatus TryNextResult(out bool more) - { - TdsOperationStatus result; - SqlStatistics statistics = null; - using (TryEventScope.Create(" {0}", ObjectID)) - { - RuntimeHelpers.PrepareConstrainedRegions(); - - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - statistics = SqlStatistics.StartTimer(Statistics); - - SetTimeout(_defaultTimeoutMilliseconds); - - if (IsClosed) - { - throw ADP.DataReaderClosed(nameof(NextResult)); - } - _fieldNameLookup = null; - - bool success = false; // WebData 100390 - _hasRows = false; // reset HasRows - - // if we are specifically only processing a single result, then read all the results off the wire and detach - if (IsCommandBehavior(CommandBehavior.SingleResult)) - { - result = TryCloseInternal(closeReader: false); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - - // In the case of not closing the reader, null out the metadata AFTER - // CloseInternal finishes - since CloseInternal may go to the wire - // and use the metadata. - ClearMetaData(); - more = success; - return TdsOperationStatus.Done; - } - - if (_parser != null) - { - // if there are more rows, then skip them, the user wants the next result - bool moreRows = true; - while (moreRows) - { - result = TryReadInternal(false, out moreRows); - if (result != TdsOperationStatus.Done) - { - // don't reset set the timeout value - more = false; - return result; - } - } - } - - // we may be done, so continue only if we have not detached ourselves from the parser - if (_parser != null) - { - bool moreResults; - result = TryHasMoreResults(out moreResults); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - if (moreResults) - { - _metaDataConsumed = false; - _browseModeInfoConsumed = false; - - switch (_altRowStatus) - { - case ALTROWSTATUS.AltRow: - int altRowId; - result = _parser.TryGetAltRowId(_stateObj, out altRowId); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - _SqlMetaDataSet altMetaDataSet = _altMetaDataSetCollection.GetAltMetaData(altRowId); - if (altMetaDataSet != null) - { - _metaData = altMetaDataSet; - } - Debug.Assert((_metaData != null), "Can't match up altrowmetadata"); - break; - case ALTROWSTATUS.Done: - // restore the row-metaData - _metaData = _altMetaDataSetCollection.metaDataSet; - Debug.Assert(_altRowStatus == ALTROWSTATUS.Done, "invalid AltRowStatus"); - _altRowStatus = ALTROWSTATUS.Null; - break; - default: - result = TryConsumeMetaData(); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - if (_metaData == null) - { - more = false; - return TdsOperationStatus.Done; - } - break; - } - - success = true; - } - else - { - // detach the parser from this reader now - result = TryCloseInternal(closeReader: false); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - - // In the case of not closing the reader, null out the metadata AFTER - // CloseInternal finishes - since CloseInternal may go to the wire - // and use the metadata. - result = TrySetMetaData(null, false); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - } - } - else - { - // Clear state in case of Read calling CloseInternal() then user calls NextResult() - // MDAC 81986. Or, also the case where the Read() above will do essentially the same - // thing. - ClearMetaData(); - } - - more = success; - return TdsOperationStatus.Done; - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.StackOverflowException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.Threading.ThreadAbortException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - } - - /// - // user must call Read() to position on the first row - override public bool Read() - { - if (_currentTask != null) - { - throw SQL.PendingBeginXXXExists(); - } - - bool more; - - Debug.Assert(_stateObj == null || _stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - TdsOperationStatus result = TryReadInternal(true, out more); - - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - return more; - } - - // user must call Read() to position on the first row - private TdsOperationStatus TryReadInternal(bool setTimeout, out bool more) - { - SqlStatistics statistics = null; - using (TryEventScope.Create(" {0}", ObjectID)) - { - RuntimeHelpers.PrepareConstrainedRegions(); - - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - TdsOperationStatus result; - statistics = SqlStatistics.StartTimer(Statistics); - - if (_parser != null) - { - if (setTimeout) - { - SetTimeout(_defaultTimeoutMilliseconds); - } - if (_sharedState._dataReady) - { - result = TryCleanPartialRead(); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - } - - // clear out our buffers - SqlBuffer.Clear(_data); - - _sharedState._nextColumnHeaderToRead = 0; - _sharedState._nextColumnDataToRead = 0; - _sharedState._columnDataBytesRemaining = -1; // unknown - _lastColumnWithDataChunkRead = -1; - - if (!_haltRead) - { - bool moreRows; - result = TryHasMoreRows(out moreRows); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - if (moreRows) - { - // read the row from the backend (unless it's an altrow were the marker is already inside the altrow ...) - while (_stateObj.HasPendingData) - { - if (_altRowStatus != ALTROWSTATUS.AltRow) - { - // if this is an ordinary row we let the run method consume the ROW token - result = _parser.TryRun(RunBehavior.ReturnImmediately, _command, this, null, _stateObj, out _sharedState._dataReady); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - if (_sharedState._dataReady) - { - break; - } - } - else - { - // ALTROW token and AltrowId are already consumed ... - Debug.Assert(_altRowStatus == ALTROWSTATUS.AltRow, "invalid AltRowStatus"); - _altRowStatus = ALTROWSTATUS.Done; - _sharedState._dataReady = true; - break; - } - } - if (_sharedState._dataReady) - { - _haltRead = IsCommandBehavior(CommandBehavior.SingleRow); - more = true; - return TdsOperationStatus.Done; - } - } - - if (!_stateObj.HasPendingData) - { - result = TryCloseInternal(closeReader: false); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - } - } - else - { - // if we did not get a row and halt is true, clean off rows of result - // success must be false - or else we could have just read off row and set - // halt to true - bool moreRows; - result = TryHasMoreRows(out moreRows); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - while (moreRows) - { - // if we are in SingleRow mode, and we've read the first row, - // read the rest of the rows, if any - while (_stateObj.HasPendingData && !_sharedState._dataReady) - { - result = _parser.TryRun(RunBehavior.ReturnImmediately, _command, this, null, _stateObj, out _sharedState._dataReady); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - } - - if (_sharedState._dataReady) - { - result = TryCleanPartialRead(); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - } - - // clear out our buffers - SqlBuffer.Clear(_data); - - _sharedState._nextColumnHeaderToRead = 0; - - result = TryHasMoreRows(out moreRows); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - } - - // reset haltRead - _haltRead = false; - } - } - else if (IsClosed) - { - throw ADP.DataReaderClosed(nameof(Read)); - } - more = false; - -#if DEBUG - if ((!_sharedState._dataReady) && (_stateObj.HasPendingData)) - { - byte token; - result = _stateObj.TryPeekByte(out token); - if (result != TdsOperationStatus.Done) - { - return result; - } - - Debug.Assert(TdsParser.IsValidTdsToken(token), $"DataReady is false, but next token is invalid: {token,-2:X2}"); - } -#endif - - return TdsOperationStatus.Done; - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (OutOfMemoryException e) - { - _isClosed = true; - SqlConnection con = _connection; - if (con != null) - { - con.Abort(e); - } - throw; - } - catch (StackOverflowException e) - { - _isClosed = true; - SqlConnection con = _connection; - if (con != null) - { - con.Abort(e); - } - throw; - } - catch (System.Threading.ThreadAbortException e) - { - _isClosed = true; - SqlConnection con = _connection; - if (con != null) - { - con.Abort(e); - } - throw; - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - } - - private void ReadColumn(int i, bool setTimeout = true, bool allowPartiallyReadColumn = false) - { - if (_currentTask != null) - { - throw ADP.AsyncOperationPending(); - } - - Debug.Assert(_stateObj == null || _stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - TdsOperationStatus result = TryReadColumn(i, setTimeout, allowPartiallyReadColumn); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - } - - private TdsOperationStatus TryReadColumn(int i, bool setTimeout, bool allowPartiallyReadColumn = false, bool forStreaming = false) - { - CheckDataIsReady(columnIndex: i, permitAsync: true, allowPartiallyReadColumn: allowPartiallyReadColumn, methodName: nameof(CheckDataIsReady)); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - Debug.Assert(_sharedState._nextColumnHeaderToRead <= _metaData.Length, "_sharedState._nextColumnHeaderToRead too large"); - Debug.Assert(_sharedState._nextColumnDataToRead <= _metaData.Length, "_sharedState._nextColumnDataToRead too large"); - - if (setTimeout) - { - SetTimeout(_defaultTimeoutMilliseconds); - } - - TdsOperationStatus result = TryReadColumnInternal(i, readHeaderOnly: false, forStreaming: forStreaming); - if (result != TdsOperationStatus.Done) - { - return result; - } - - Debug.Assert(_data[i] != null, " data buffer is null?"); - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.StackOverflowException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.Threading.ThreadAbortException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - - return TdsOperationStatus.Done; - } - - private TdsOperationStatus TryReadColumnData() - { - // If we've already read the value (because it was NULL) we don't - // bother to read here. - if (!_data[_sharedState._nextColumnDataToRead].IsNull) - { - _SqlMetaData columnMetaData = _metaData[_sharedState._nextColumnDataToRead]; - - TdsOperationStatus result = _parser.TryReadSqlValue(_data[_sharedState._nextColumnDataToRead], columnMetaData, (int)_sharedState._columnDataBytesRemaining, _stateObj, - _command != null ? _command.ColumnEncryptionSetting : SqlCommandColumnEncryptionSetting.UseConnectionSetting, - columnMetaData.column, _command); - if (result != TdsOperationStatus.Done) - { - // will read UDTs as VARBINARY. - return result; - } - _sharedState._columnDataBytesRemaining = 0; - } - _sharedState._nextColumnDataToRead++; - return TdsOperationStatus.Done; - } - - private void ReadColumnHeader(int i) - { - Debug.Assert(_stateObj == null || _stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - TdsOperationStatus result = TryReadColumnHeader(i); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - } - - private TdsOperationStatus TryReadColumnHeader(int i) - { - if (!_sharedState._dataReady) - { - throw SQL.InvalidRead(); - } - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#endif //DEBUG - return TryReadColumnInternal(i, readHeaderOnly: true, forStreaming: false); -#if DEBUG - } - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.StackOverflowException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - catch (System.Threading.ThreadAbortException e) - { - _isClosed = true; - if (_connection != null) - { - _connection.Abort(e); - } - throw; - } - } - - internal TdsOperationStatus TryReadColumnInternal(int i, bool readHeaderOnly/* = false*/, bool forStreaming) - { - AssertReaderState(requireData: true, permitAsync: true, columnIndex: i); - - // Check if we've already read the header already - if (i < _sharedState._nextColumnHeaderToRead) - { - // Read the header, but we need to read the data - if ((i == _sharedState._nextColumnDataToRead) && (!readHeaderOnly)) - { - return TryReadColumnData(); - } - // Else we've already read the data, or we're reading the header only - else - { - // Ensure that, if we've read past the column, then we did store its data - Debug.Assert(i == _sharedState._nextColumnDataToRead || // Either we haven't read the column yet - ((i + 1 < _sharedState._nextColumnDataToRead) && (IsCommandBehavior(CommandBehavior.SequentialAccess))) || // Or we're in sequential mode and we've read way past the column (i.e. it was not the last column we read) - (!_data[i].IsEmpty || _data[i].IsNull) || // Or we should have data stored for the column (unless the column was null) - (_metaData[i].type == SqlDbType.Timestamp), // Or Dev11 Bug #336820, Dev10 Bug #479607 (SqlClient: IsDBNull always returns false for timestamp datatype) - // Due to a bug in TdsParser.GetNullSqlValue, Timestamps' IsNull is not correctly set - so we need to bypass the check - "Gone past column, be we have no data stored for it"); - return TdsOperationStatus.Done; - } - } - - Debug.Assert(_data[i].IsEmpty || _data[i].IsNull, "re-reading column value?"); - - // If we're in sequential access mode, we can safely clear out any - // data from the previous column. - TdsOperationStatus result; - bool isSequentialAccess = IsCommandBehavior(CommandBehavior.SequentialAccess); - if (isSequentialAccess) - { - if (0 < _sharedState._nextColumnDataToRead) - { - _data[_sharedState._nextColumnDataToRead - 1].Clear(); - } - - // Only wipe out the blob objects if they aren't for a 'future' column (i.e. we haven't read up to them yet) - if ((_lastColumnWithDataChunkRead > -1) && (i > _lastColumnWithDataChunkRead)) - { - CloseActiveSequentialStreamAndTextReader(); - } - } - else if (_sharedState._nextColumnDataToRead < _sharedState._nextColumnHeaderToRead) - { - // We read the header but not the column for the previous column - result = TryReadColumnData(); - if (result != TdsOperationStatus.Done) - { - return result; - } - Debug.Assert(_sharedState._nextColumnDataToRead == _sharedState._nextColumnHeaderToRead); - } - - // if we still have bytes left from the previous blob read, clear the wire and reset - result = TryResetBlobState(); - if (result != TdsOperationStatus.Done) - { - return result; - } - - do - { - _SqlMetaData columnMetaData = _metaData[_sharedState._nextColumnHeaderToRead]; - - if (isSequentialAccess) - { - if (_sharedState._nextColumnHeaderToRead < i) - { - // SkipValue is no-op if the column appears in NBC bitmask - // if not, it skips regular and PLP types - result = _parser.TrySkipValue(columnMetaData, _sharedState._nextColumnHeaderToRead, _stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - - _sharedState._nextColumnDataToRead = _sharedState._nextColumnHeaderToRead; - _sharedState._nextColumnHeaderToRead++; - } - else if (_sharedState._nextColumnHeaderToRead == i) - { - bool isNull; - ulong dataLength; - result = _parser.TryProcessColumnHeader(columnMetaData, _stateObj, _sharedState._nextColumnHeaderToRead, out isNull, out dataLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - - _sharedState._nextColumnDataToRead = _sharedState._nextColumnHeaderToRead; - _sharedState._nextColumnHeaderToRead++; // We read this one - _sharedState._columnDataBytesRemaining = (long)dataLength; - - if (isNull) - { - if (columnMetaData.type != SqlDbType.Timestamp) - { - TdsParser.GetNullSqlValue(_data[_sharedState._nextColumnDataToRead], - columnMetaData, - _command != null ? _command.ColumnEncryptionSetting : SqlCommandColumnEncryptionSetting.UseConnectionSetting, - _parser.Connection); - } - } - else - { - if (!readHeaderOnly && !forStreaming) - { - // If we're in sequential mode try to read the data and then if it succeeds update shared - // state so there are no remaining bytes and advance the next column to read - result = _parser.TryReadSqlValue(_data[_sharedState._nextColumnDataToRead], columnMetaData, (int)dataLength, _stateObj, - _command != null ? _command.ColumnEncryptionSetting : SqlCommandColumnEncryptionSetting.UseConnectionSetting, - columnMetaData.column); - if (result != TdsOperationStatus.Done) - { - // will read UDTs as VARBINARY. - return result; - } - _sharedState._columnDataBytesRemaining = 0; - _sharedState._nextColumnDataToRead++; - } - else - { - _sharedState._columnDataBytesRemaining = (long)dataLength; - } - } - } - else - { - Debug.Assert(false, "We have read past the column somehow, this is an error"); - } - } - else - { - bool isNull; - ulong dataLength; - result = _parser.TryProcessColumnHeader(columnMetaData, _stateObj, _sharedState._nextColumnHeaderToRead, out isNull, out dataLength); - if (result != TdsOperationStatus.Done) - { - return result; - } - - _sharedState._nextColumnDataToRead = _sharedState._nextColumnHeaderToRead; - _sharedState._nextColumnHeaderToRead++; // We read this one - - // Trigger new behavior for RowVersion to send DBNull.Value by allowing entry for Timestamp or discard entry for Timestamp for legacy support. - // if LegacyRowVersionNullBehavior is enabled, Timestamp type must enter "else" block. - if (isNull && (!LocalAppContextSwitches.LegacyRowVersionNullBehavior || columnMetaData.type != SqlDbType.Timestamp)) - { - TdsParser.GetNullSqlValue(_data[_sharedState._nextColumnDataToRead], - columnMetaData, - _command != null ? _command.ColumnEncryptionSetting : SqlCommandColumnEncryptionSetting.UseConnectionSetting, - _parser.Connection); - - if (!readHeaderOnly) - { - _sharedState._nextColumnDataToRead++; - } - } - else - { - if ((i > _sharedState._nextColumnDataToRead) || (!readHeaderOnly)) - { - // If we're not in sequential access mode, we have to - // save the data we skip over so that the consumer - // can read it out of order - result = _parser.TryReadSqlValue(_data[_sharedState._nextColumnDataToRead], columnMetaData, (int)dataLength, _stateObj, - _command != null ? _command.ColumnEncryptionSetting : SqlCommandColumnEncryptionSetting.UseConnectionSetting, - columnMetaData.column, _command); - if (result != TdsOperationStatus.Done) - { - // will read UDTs as VARBINARY. - return result; - } - _sharedState._nextColumnDataToRead++; - } - else - { - _sharedState._columnDataBytesRemaining = (long)dataLength; - } - } - } - - if (_snapshot != null) - { - // reset snapshot to save memory use. We can safely do that here because all SqlDataReader values are stable. - // The retry logic can use the current values to get back to the right state. - _snapshot = null; - PrepareAsyncInvocation(useSnapshot: true); - } - } while (_sharedState._nextColumnHeaderToRead <= i); - - return TdsOperationStatus.Done; - } - - // Estimates if there is enough data available to read the number of columns requested - private bool WillHaveEnoughData(int targetColumn, bool headerOnly = false) - { - AssertReaderState(requireData: true, permitAsync: true, columnIndex: targetColumn); - - if ((_lastColumnWithDataChunkRead == _sharedState._nextColumnDataToRead) && (_metaData[_lastColumnWithDataChunkRead].metaType.IsPlp)) - { - // In the middle of reading a Plp - no idea how much is left - return false; - } - - int bytesRemaining = Math.Min(checked(_stateObj._inBytesRead - _stateObj._inBytesUsed), _stateObj._inBytesPacket); - - // There are some parts of our code that peeks at the next token after doing its read - // So we will make sure that there is always a spare byte for it to look at - bytesRemaining--; - - if ((targetColumn >= _sharedState._nextColumnDataToRead) && (_sharedState._nextColumnDataToRead < _sharedState._nextColumnHeaderToRead)) - { - if (_sharedState._columnDataBytesRemaining > bytesRemaining) - { - // The current column needs more data than we currently have - // NOTE: Since the Long data types (TEXT, IMAGE, NTEXT) can have a size of Int32.MaxValue we cannot simply subtract - // _columnDataBytesRemaining from bytesRemaining and then compare it to zero as this may lead to an overflow - return false; - } - else - { - // Already read the header, so subtract actual data size - bytesRemaining = checked(bytesRemaining - (int)_sharedState._columnDataBytesRemaining); - } - } - - // For each column that we need to read, subtract the size of its header and the size of its data - int currentColumn = _sharedState._nextColumnHeaderToRead; - while ((bytesRemaining >= 0) && (currentColumn <= targetColumn)) - { - // Check NBC first - if (!_stateObj.IsNullCompressionBitSet(currentColumn)) - { - // NOTE: This is mostly duplicated from TryProcessColumnHeaderNoNBC and TryGetTokenLength - var metaType = _metaData[currentColumn].metaType; - if ((metaType.IsLong) || (metaType.IsPlp) || (metaType.SqlDbType == SqlDbType.Udt) || (metaType.SqlDbType == SqlDbType.Structured)) - { - // Plp, Udt and TVP types have an unknowable size - so return that the estimate failed - return false; - } - int maxHeaderSize; - byte typeAndMask = (byte)(_metaData[currentColumn].tdsType & TdsEnums.SQLLenMask); - if ((typeAndMask == TdsEnums.SQLVarLen) || (typeAndMask == TdsEnums.SQLVarCnt)) - { - if (0 != (_metaData[currentColumn].tdsType & 0x80)) - { - // UInt16 represents size - maxHeaderSize = 2; - } - else if (0 == (_metaData[currentColumn].tdsType & 0x0c)) - { - // UInt32 represents size - maxHeaderSize = 4; - } - else - { - // Byte represents size - maxHeaderSize = 1; - } - } - else - { - maxHeaderSize = 0; - } - - bytesRemaining = checked(bytesRemaining - maxHeaderSize); - if ((currentColumn < targetColumn) || (!headerOnly)) - { - bytesRemaining = checked(bytesRemaining - _metaData[currentColumn].length); - } - } - - currentColumn++; - } - - return (bytesRemaining >= 0); - } - - // clean remainder bytes for the column off the wire - private TdsOperationStatus TryResetBlobState() - { - Debug.Assert(_stateObj != null, "null state object"); // _parser may be null at this point - AssertReaderState(requireData: true, permitAsync: true); - Debug.Assert(_sharedState._nextColumnHeaderToRead <= _metaData.Length, "_sharedState._nextColumnHeaderToRead too large"); - TdsOperationStatus result; - // If we haven't already entirely read the column - if (_sharedState._nextColumnDataToRead < _sharedState._nextColumnHeaderToRead) - { - if ((_sharedState._nextColumnHeaderToRead > 0) && (_metaData[_sharedState._nextColumnHeaderToRead - 1].metaType.IsPlp)) - { - if (_stateObj._longlen != 0) - { - result = _stateObj.Parser.TrySkipPlpValue(ulong.MaxValue, _stateObj, out _); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - if (_streamingXml != null) - { - SqlStreamingXml localSXml = _streamingXml; - _streamingXml = null; - localSXml.Close(); - } - } - else if (0 < _sharedState._columnDataBytesRemaining) - { - result = _stateObj.TrySkipLongBytes(_sharedState._columnDataBytesRemaining); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - } -#if DEBUG - else - { - Debug.Assert((_sharedState._columnDataBytesRemaining == 0 || _sharedState._columnDataBytesRemaining == -1) && _stateObj._longlen == 0, "Haven't read header yet, but column is partially read?"); - } -#endif - - _sharedState._columnDataBytesRemaining = 0; - _columnDataBytesRead = 0; - _columnDataCharsRead = 0; - _columnDataChars = null; - _columnDataCharsIndex = -1; - _stateObj._plpdecoder = null; - - return TdsOperationStatus.Done; - } - - private void CloseActiveSequentialStreamAndTextReader() - { - if (_currentStream != null) - { - _currentStream.SetClosed(); - _currentStream = null; - } - if (_currentTextReader != null) - { - _currentTextReader.SetClosed(); - _currentStream = null; - } - } - - private void RestoreServerSettings(TdsParser parser, TdsParserStateObject stateObj) - { - // turn off any set options - if (parser != null && _resetOptionsString != null) - { - // It is possible for this to be called during connection close on a - // broken connection, so check state first. - if (parser.State == TdsParserState.OpenLoggedIn) - { - SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID {0}, ActivityID '{1}'", ObjectID, ActivityCorrelator.Current); - Task executeTask = parser.TdsExecuteSQLBatch(_resetOptionsString, (_command != null) ? _command.CommandTimeout : 0, null, stateObj, sync: true); - Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes"); - - // must execute this one synchronously as we can't retry - parser.Run(RunBehavior.UntilDone, _command, this, null, stateObj); - } - _resetOptionsString = null; - } - } - - internal TdsOperationStatus TrySetAltMetaDataSet(_SqlMetaDataSet metaDataSet, bool metaDataConsumed) - { - if (_altMetaDataSetCollection == null) - { - _altMetaDataSetCollection = new _SqlMetaDataSetCollection(); - } - else if (_snapshot != null && object.ReferenceEquals(_snapshot._altMetaDataSetCollection, _altMetaDataSetCollection)) - { - _altMetaDataSetCollection = (_SqlMetaDataSetCollection)_altMetaDataSetCollection.Clone(); - } - _altMetaDataSetCollection.SetAltMetaData(metaDataSet); - _metaDataConsumed = metaDataConsumed; - if (_metaDataConsumed && _parser != null) - { - byte b; - TdsOperationStatus result = _stateObj.TryPeekByte(out b); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (TdsEnums.SQLORDER == b) - { - result = _parser.TryRun(RunBehavior.ReturnImmediately, _command, this, null, _stateObj, out _); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = _stateObj.TryPeekByte(out b); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - if (b == TdsEnums.SQLINFO) - { - try - { - _stateObj._accumulateInfoEvents = true; - result = _parser.TryRun(RunBehavior.ReturnImmediately, _command, null, null, _stateObj, out _); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - finally - { - _stateObj._accumulateInfoEvents = false; - } - - result = _stateObj.TryPeekByte(out b); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - _hasRows = IsRowToken(b); - } - if (metaDataSet != null) - { - if (_data == null || _data.Length < metaDataSet.Length) - { - _data = SqlBuffer.CreateBufferArray(metaDataSet.Length); - } - } - return TdsOperationStatus.Done; - } - - private void ClearMetaData() - { - _metaData = null; - _tableNames = null; - _fieldNameLookup = null; - _metaDataConsumed = false; - _browseModeInfoConsumed = false; - } - - internal TdsOperationStatus TrySetSensitivityClassification(SensitivityClassification sensitivityClassification) - { - SensitivityClassification = sensitivityClassification; - return TdsOperationStatus.Done; - } - - internal TdsOperationStatus TrySetMetaData(_SqlMetaDataSet metaData, bool moreInfo) - { - _metaData = metaData; - - // get rid of cached metadata info as well - _tableNames = null; - if (_metaData != null) - { - _metaData.schemaTable = null; - _data = SqlBuffer.CreateBufferArray(metaData.Length); - } - - _fieldNameLookup = null; - - if (metaData != null) - { - // we are done consuming metadata only if there is no moreInfo - if (!moreInfo) - { - _metaDataConsumed = true; - - if (_parser != null) - { // There is a valid case where parser is null - // Peek, and if row token present, set _hasRows true since there is a - // row in the result - byte b; - TdsOperationStatus result = _stateObj.TryPeekByte(out b); - if (result != TdsOperationStatus.Done) - { - return result; - } - - // simply rip the order token off the wire - if (b == TdsEnums.SQLORDER) - { - // same logic as SetAltMetaDataSet - result = _parser.TryRun(RunBehavior.ReturnImmediately, null, null, null, _stateObj, out _); - if (result != TdsOperationStatus.Done) - { - return result; - } - result = _stateObj.TryPeekByte(out b); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - if (b == TdsEnums.SQLINFO) - { - // VSTFDEVDIV713926 - // We are accumulating informational events and fire them at next - // TdsParser.Run purely to avoid breaking change - try - { - _stateObj._accumulateInfoEvents = true; - result = _parser.TryRun(RunBehavior.ReturnImmediately, null, null, null, _stateObj, out _); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - finally - { - _stateObj._accumulateInfoEvents = false; - } - - result = _stateObj.TryPeekByte(out b); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - _hasRows = IsRowToken(b); - if (TdsEnums.SQLALTMETADATA == b) - { - _metaDataConsumed = false; - } - } - } - } - else - { - _metaDataConsumed = false; - } - - _browseModeInfoConsumed = false; - return TdsOperationStatus.Done; - } - - private void SetTimeout(long timeoutMilliseconds) - { - // WebData 111653,112003 -- we now set timeouts per operation, not - // per command (it's not supposed to be a cumulative per command). - TdsParserStateObject stateObj = _stateObj; - if (stateObj != null) - { - stateObj.SetTimeoutMilliseconds(timeoutMilliseconds); - } - } - - private bool HasActiveStreamOrTextReaderOnColumn(int columnIndex) - { - bool active = false; - - active |= ((_currentStream != null) && (_currentStream.ColumnIndex == columnIndex)); - active |= ((_currentTextReader != null) && (_currentTextReader.ColumnIndex == columnIndex)); - - return active; - } - - private void CheckMetaDataIsReady() - { - if (_currentTask != null) - { - throw ADP.AsyncOperationPending(); - } - if (MetaData == null) - { - throw SQL.InvalidRead(); - } - } - - private void CheckMetaDataIsReady(int columnIndex, bool permitAsync = false) - { - if ((!permitAsync) && (_currentTask != null)) - { - throw ADP.AsyncOperationPending(); - } - if (MetaData == null) - { - throw SQL.InvalidRead(); - } - if ((columnIndex < 0) || (columnIndex >= _metaData.Length)) - { - throw ADP.IndexOutOfRange(); - } - } - - private void CheckDataIsReady() - { - if (_currentTask != null) - { - throw ADP.AsyncOperationPending(); - } - Debug.Assert(!_sharedState._dataReady || _metaData != null, "Data is ready, but there is no metadata?"); - if ((!_sharedState._dataReady) || (_metaData == null)) - { - throw SQL.InvalidRead(); - } - } - - private void CheckHeaderIsReady(int columnIndex, bool permitAsync = false, [CallerMemberName] string methodName = null) - { - if (_isClosed) - { - throw ADP.DataReaderClosed(methodName ?? nameof(CheckHeaderIsReady)); - } - if ((!permitAsync) && (_currentTask != null)) - { - throw ADP.AsyncOperationPending(); - } - Debug.Assert(!_sharedState._dataReady || _metaData != null, "Data is ready, but there is no metadata?"); - if ((!_sharedState._dataReady) || (_metaData == null)) - { - throw SQL.InvalidRead(); - } - if ((columnIndex < 0) || (columnIndex >= _metaData.Length)) - { - throw ADP.IndexOutOfRange(); - } - if ((IsCommandBehavior(CommandBehavior.SequentialAccess)) && // Only for sequential access - ((_sharedState._nextColumnHeaderToRead > columnIndex + 1) || (_lastColumnWithDataChunkRead > columnIndex))) - { // Read past column - throw ADP.NonSequentialColumnAccess(columnIndex, Math.Max(_sharedState._nextColumnHeaderToRead - 1, _lastColumnWithDataChunkRead)); - } - } - - private void CheckDataIsReady(int columnIndex, bool allowPartiallyReadColumn = false, bool permitAsync = false, [CallerMemberName] string methodName = null) - { - if (_isClosed) - { - throw ADP.DataReaderClosed(methodName ?? nameof(CheckDataIsReady)); - } - if ((!permitAsync) && (_currentTask != null)) - { - throw ADP.AsyncOperationPending(); - } - Debug.Assert(!_sharedState._dataReady || _metaData != null, "Data is ready, but there is no metadata?"); - if ((!_sharedState._dataReady) || (_metaData == null)) - { - throw SQL.InvalidRead(); - } - if ((columnIndex < 0) || (columnIndex >= _metaData.Length)) - { - throw ADP.IndexOutOfRange(); - } - if ((IsCommandBehavior(CommandBehavior.SequentialAccess)) && // Only for sequential access - ((_sharedState._nextColumnDataToRead > columnIndex) || (_lastColumnWithDataChunkRead > columnIndex) || // Read past column - ((!allowPartiallyReadColumn) && (_lastColumnWithDataChunkRead == columnIndex)) || // Partially read column - ((allowPartiallyReadColumn) && (HasActiveStreamOrTextReaderOnColumn(columnIndex))))) - { // Has a Stream or TextReader on a partially-read column - throw ADP.NonSequentialColumnAccess(columnIndex, Math.Max(_sharedState._nextColumnDataToRead, _lastColumnWithDataChunkRead + 1)); - } - } - - [Conditional("DEBUG")] - private void AssertReaderState(bool requireData, bool permitAsync, int? columnIndex = null, bool enforceSequentialAccess = false) - { - Debug.Assert(!_sharedState._dataReady || _metaData != null, "Data is ready, but there is no metadata?"); - Debug.Assert(permitAsync || _currentTask == null, "Call while async operation is pending"); - Debug.Assert(_metaData != null, "_metaData is null, check MetaData before calling this method"); - Debug.Assert(!requireData || _sharedState._dataReady, "No data is ready to be read"); - if (columnIndex.HasValue) - { - Debug.Assert(columnIndex.Value >= 0 && columnIndex.Value < _metaData.Length, "Invalid column index"); - Debug.Assert((!enforceSequentialAccess) || (!IsCommandBehavior(CommandBehavior.SequentialAccess)) || ((_sharedState._nextColumnDataToRead <= columnIndex) && (_lastColumnWithDataChunkRead <= columnIndex)), "Already read past column"); - } - } - - /// - public override Task NextResultAsync(CancellationToken cancellationToken) - { - using (TryEventScope.Create(" {0}", ObjectID)) - using (var registrationHolder = new DisposableTemporaryOnStack()) - { - TaskCompletionSource source = new TaskCompletionSource(); - - if (IsClosed) - { - source.SetException(ADP.ExceptionWithStackTrace(ADP.DataReaderClosed())); - return source.Task; - } - - if (cancellationToken.CanBeCanceled) - { - if (cancellationToken.IsCancellationRequested) - { - source.SetCanceled(); - return source.Task; - } - registrationHolder.Set(cancellationToken.Register(SqlCommand.s_cancelIgnoreFailure, _command)); - } - - Task original = Interlocked.CompareExchange(ref _currentTask, source.Task, null); - if (original != null) - { - source.SetException(ADP.ExceptionWithStackTrace(SQL.PendingBeginXXXExists())); - return source.Task; - } - - // Check if cancellation due to close is requested (this needs to be done after setting _currentTask) - if (_cancelAsyncOnCloseToken.IsCancellationRequested) - { - source.SetCanceled(); - _currentTask = null; - return source.Task; - } - - return InvokeAsyncCall(new HasNextResultAsyncCallContext(this, source, registrationHolder.Take())); - } - } - - private static Task NextResultAsyncExecute(Task task, object state) - { - HasNextResultAsyncCallContext context = (HasNextResultAsyncCallContext)state; - if (task != null) - { - SqlClientEventSource.Log.TryTraceEvent(" attempt retry {0}", context.Reader.ObjectID); - context.Reader.PrepareForAsyncContinuation(); - } - - if (context.Reader.TryNextResult(out bool more) == TdsOperationStatus.Done) - { - // completed - return more ? ADP.TrueTask : ADP.FalseTask; - } - - return context.Reader.ExecuteAsyncCall(context); - } - - // NOTE: This will return null if it completed sequentially - // If this returns null, then you can use bytesRead to see how many bytes were read - otherwise bytesRead should be ignored - internal Task GetBytesAsync(int columnIndex, byte[] buffer, int index, int length, int timeout, CancellationToken cancellationToken, out int bytesRead) - { - AssertReaderState(requireData: true, permitAsync: true, columnIndex: columnIndex, enforceSequentialAccess: true); - Debug.Assert(IsCommandBehavior(CommandBehavior.SequentialAccess)); - - bytesRead = 0; - if (IsClosed) - { - return Task.FromException(ADP.ExceptionWithStackTrace(ADP.DataReaderClosed())); - } - - if (_currentTask != null) - { - return Task.FromException(ADP.ExceptionWithStackTrace(ADP.AsyncOperationPending())); - } - - if (cancellationToken.CanBeCanceled) - { - if (cancellationToken.IsCancellationRequested) - { - return null; - } - } - - var context = new GetBytesAsyncCallContext(this) - { - _columnIndex = columnIndex, - _buffer = buffer, - _index = index, - _length = length, - _timeout = timeout, - _cancellationToken = cancellationToken, - }; - - // Check if we need to skip columns - Debug.Assert(_sharedState._nextColumnDataToRead <= _lastColumnWithDataChunkRead, "Non sequential access"); - if ((_sharedState._nextColumnHeaderToRead <= _lastColumnWithDataChunkRead) || (_sharedState._nextColumnDataToRead < _lastColumnWithDataChunkRead)) - { - TaskCompletionSource source = new TaskCompletionSource(); - Task original = Interlocked.CompareExchange(ref _currentTask, source.Task, null); - if (original != null) - { - source.SetException(ADP.ExceptionWithStackTrace(ADP.AsyncOperationPending())); - return source.Task; - } - - // Timeout - CancellationToken timeoutToken = CancellationToken.None; - CancellationTokenSource timeoutCancellationSource = null; - if (timeout > 0) - { - timeoutCancellationSource = new CancellationTokenSource(); - timeoutCancellationSource.CancelAfter(timeout); - timeoutToken = timeoutCancellationSource.Token; - } - - - PrepareAsyncInvocation(useSnapshot: true); - - context.Set(this, source, timeoutCancellationSource); - context._timeoutToken = timeoutToken; - - return InvokeAsyncCall(context); - } - else - { - // We're already at the correct column, just read the data - context._mode = GetBytesAsyncCallContext.OperationMode.Read; - - // Switch to async - PrepareAsyncInvocation(useSnapshot: false); - - try - { - return GetBytesAsyncReadDataStage(context, false, out bytesRead); - } - catch - { - CleanupAfterAsyncInvocation(); - throw; - } - } - } - - private static Task GetBytesAsyncSeekExecute(Task task, object state) - { - GetBytesAsyncCallContext context = (GetBytesAsyncCallContext)state; - SqlDataReader reader = context.Reader; - - Debug.Assert(context._mode == GetBytesAsyncCallContext.OperationMode.Seek, "context.mode must be Seek to check if seeking can resume"); - - if (task != null) - { - reader.PrepareForAsyncContinuation(); - } - - // Prepare for stateObj timeout - reader.SetTimeout(reader._defaultTimeoutMilliseconds); - - if (reader.TryReadColumnHeader(context._columnIndex) == TdsOperationStatus.Done) - { - // Only once we have read up to where we need to be can we check the cancellation tokens (otherwise we will be in an unknown state) - - if (context._cancellationToken.IsCancellationRequested) - { - // User requested cancellation - return Task.FromCanceled(context._cancellationToken); - } - else if (context._timeoutToken.IsCancellationRequested) - { - // Timeout - return Task.FromException(ADP.ExceptionWithStackTrace(ADP.IO(SQLMessage.Timeout()))); - } - else - { - // Up to the correct column - continue to read - context._mode = GetBytesAsyncCallContext.OperationMode.Read; - reader.SwitchToAsyncWithoutSnapshot(); - int totalBytesRead; - var readTask = reader.GetBytesAsyncReadDataStage(context, true, out totalBytesRead); - if (readTask == null) - { - // Completed synchronously - return Task.FromResult(totalBytesRead); - } - else - { - return readTask; - } - } - } - else - { - return reader.ExecuteAsyncCall(context); - } - } - - private static Task GetBytesAsyncReadExecute(Task task, object state) - { - var context = (GetBytesAsyncCallContext)state; - SqlDataReader reader = context.Reader; - - Debug.Assert(context._mode == GetBytesAsyncCallContext.OperationMode.Read, "context.mode must be Read to check if read can resume"); - - reader.PrepareForAsyncContinuation(); - - if (context._cancellationToken.IsCancellationRequested) - { - // User requested cancellation - return Task.FromCanceled(context._cancellationToken); - } - else if (context._timeoutToken.IsCancellationRequested) - { - // Timeout - return Task.FromException(ADP.ExceptionWithStackTrace(ADP.IO(SQLMessage.Timeout()))); - } - else - { - // Prepare for stateObj timeout - reader.SetTimeout(reader._defaultTimeoutMilliseconds); - - int bytesReadThisIteration; - TdsOperationStatus result = reader.TryGetBytesInternalSequential( - context._columnIndex, - context._buffer, - context._index + context._totalBytesRead, - context._length - context._totalBytesRead, - out bytesReadThisIteration - ); - context._totalBytesRead += bytesReadThisIteration; - Debug.Assert(context._totalBytesRead <= context._length, "Read more bytes than required"); - - if (result == TdsOperationStatus.Done) - { - return Task.FromResult(context._totalBytesRead); - } - else - { - return reader.ExecuteAsyncCall(context); - } - } - } - - private Task GetBytesAsyncReadDataStage(GetBytesAsyncCallContext context, bool isContinuation, out int bytesRead) - { - Debug.Assert(context._mode == GetBytesAsyncCallContext.OperationMode.Read, "context.Mode must be Read to read data"); - - _lastColumnWithDataChunkRead = context._columnIndex; - TaskCompletionSource source = null; - - // Prepare for stateObj timeout - SetTimeout(_defaultTimeoutMilliseconds); - - // Try to read without any continuations (all the data may already be in the stateObj's buffer) - TdsOperationStatus filledBuffer = context.Reader.TryGetBytesInternalSequential( - context._columnIndex, - context._buffer, - context._index + context._totalBytesRead, - context._length - context._totalBytesRead, - out bytesRead - ); - context._totalBytesRead += bytesRead; - Debug.Assert(context._totalBytesRead <= context._length, "Read more bytes than required"); - - if (filledBuffer != TdsOperationStatus.Done) - { - // This will be the 'state' for the callback - if (!isContinuation) - { - // This is the first async operation which is happening - setup the _currentTask and timeout - Debug.Assert(context.Source == null, "context._source should not be non-null when trying to change to async"); - source = new TaskCompletionSource(); - Task original = Interlocked.CompareExchange(ref _currentTask, source.Task, null); - if (original != null) - { - source.SetException(ADP.ExceptionWithStackTrace(ADP.AsyncOperationPending())); - return source.Task; - } - context.Source = source; - // Check if cancellation due to close is requested (this needs to be done after setting _currentTask) - if (_cancelAsyncOnCloseToken.IsCancellationRequested) - { - source.SetCanceled(); - _currentTask = null; - return source.Task; - } - - // Timeout - Debug.Assert(context._timeoutToken == CancellationToken.None, "TimeoutToken is set when GetBytesAsyncReadDataStage is not a continuation"); - if (context._timeout > 0) - { - CancellationTokenSource timeoutCancellationSource = new CancellationTokenSource(); - timeoutCancellationSource.CancelAfter(context._timeout); - Debug.Assert(context.Disposable is null, "setting context.disposable would lose the previous disposable"); - context.Disposable = timeoutCancellationSource; - context._timeoutToken = timeoutCancellationSource.Token; - } - } - - Task retryTask = ExecuteAsyncCall(context); - if (isContinuation) - { - // Let the caller handle cleanup/completing - return retryTask; - } - else - { - Debug.Assert(context.Source != null, "context._source should not be null when continuing"); - // setup for cleanup/completing - retryTask.ContinueWith( - continuationAction: SqlDataReaderBaseAsyncCallContext.s_completeCallback, - state: context, - TaskScheduler.Default - ); - return source.Task; - } - } - - if (!isContinuation) - { - // If this is the first async op, we need to cleanup - CleanupAfterAsyncInvocation(); - } - // Completed synchronously, return null - return null; - } - - /// - public override Task ReadAsync(CancellationToken cancellationToken) - { - using (TryEventScope.Create(" {0}", ObjectID)) - using (var registrationHolder = new DisposableTemporaryOnStack()) - { - if (IsClosed) - { - return Task.FromException(ADP.ExceptionWithStackTrace(ADP.DataReaderClosed())); - } - - // Register first to catch any already expired tokens to be able to trigger cancellation event. - if (cancellationToken.CanBeCanceled) - { - registrationHolder.Set(cancellationToken.Register(SqlCommand.s_cancelIgnoreFailure, _command)); - } - - // If user's token is canceled, return a canceled task - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - // Check for existing async - if (_currentTask != null) - { - return Task.FromException(ADP.ExceptionWithStackTrace(SQL.PendingBeginXXXExists())); - } - - // These variables will be captured in moreFunc so that we can skip searching for a row token once one has been read - bool rowTokenRead = false; - bool more = false; - - // Shortcut, do we have enough data to immediately do the ReadAsync? - try - { - // First, check if we can finish reading the current row - // NOTE: If we are in SingleRow mode and we've read that single row (i.e. _haltRead == true), then skip the shortcut - if ((!_haltRead) && ((!_sharedState._dataReady) || (WillHaveEnoughData(_metaData.Length - 1)))) - { -#if DEBUG - try - { - _stateObj._shouldHaveEnoughData = true; -#endif - if (_sharedState._dataReady) - { - // Clean off current row - CleanPartialReadReliable(); - } - - // If there a ROW token ready (as well as any metadata for the row) - if (_stateObj.IsRowTokenReady()) - { - // Read the ROW token - TdsOperationStatus result = TryReadInternal(true, out more); - Debug.Assert(result == TdsOperationStatus.Done, "Should not have run out of data"); - - rowTokenRead = true; - if (more) - { - // Sequential mode, nothing left to do - if (IsCommandBehavior(CommandBehavior.SequentialAccess)) - { - return ADP.TrueTask; - } - // For non-sequential, check if we can read the row data now - else if (WillHaveEnoughData(_metaData.Length - 1)) - { - // Read row data - result = TryReadColumn(_metaData.Length - 1, setTimeout: true); - Debug.Assert(result == TdsOperationStatus.Done, "Should not have run out of data"); - return ADP.TrueTask; - } - } - else - { - // No data left, return - return ADP.FalseTask; - } - } -#if DEBUG - } - finally - { - _stateObj._shouldHaveEnoughData = false; - } -#endif - } - } - catch (Exception ex) - { - if (!ADP.IsCatchableExceptionType(ex)) - { - throw; - } - return Task.FromException(ex); - } - - TaskCompletionSource source = new TaskCompletionSource(); - Task original = Interlocked.CompareExchange(ref _currentTask, source.Task, null); - if (original != null) - { - source.SetException(ADP.ExceptionWithStackTrace(SQL.PendingBeginXXXExists())); - return source.Task; - } - - // Check if cancellation due to close is requested (this needs to be done after setting _currentTask) - if (_cancelAsyncOnCloseToken.IsCancellationRequested) - { - source.SetCanceled(); - _currentTask = null; - return source.Task; - } - - var context = Interlocked.Exchange(ref _cachedReadAsyncContext, null) ?? new ReadAsyncCallContext(); - - Debug.Assert(context.Reader == null && context.Source == null && context.Disposable == default, "cached ReadAsyncCallContext was not properly disposed"); - - context.Set(this, source, registrationHolder.Take()); - context._hasMoreData = more; - context._hasReadRowToken = rowTokenRead; - - PrepareAsyncInvocation(useSnapshot: true); - - return InvokeAsyncCall(context); - } - } - - private static Task ReadAsyncExecute(Task task, object state) - { - var context = (ReadAsyncCallContext)state; - SqlDataReader reader = context.Reader; - ref bool hasMoreData = ref context._hasMoreData; - ref bool hasReadRowToken = ref context._hasReadRowToken; - - if (task != null) - { - reader.PrepareForAsyncContinuation(); - } - - if (hasReadRowToken || (reader.TryReadInternal(true, out hasMoreData) == TdsOperationStatus.Done)) - { - // If there are no more rows, or this is Sequential Access, then we are done - if (!hasMoreData || (reader._commandBehavior & CommandBehavior.SequentialAccess) == CommandBehavior.SequentialAccess) - { - // completed - return hasMoreData ? ADP.TrueTask : ADP.FalseTask; - } - else - { - // First time reading the row token - update the snapshot - if (!hasReadRowToken) - { - hasReadRowToken = true; - reader._snapshot = null; - reader.PrepareAsyncInvocation(useSnapshot: true); - } - - // if non-sequentialaccess then read entire row before returning - TdsOperationStatus result = reader.TryReadColumn(reader._metaData.Length - 1, true); - if (result == TdsOperationStatus.Done) - { - // completed - return ADP.TrueTask; - } - } - } - - return reader.ExecuteAsyncCall(context); - } - - private void SetCachedReadAsyncCallContext(ReadAsyncCallContext instance) - { - Interlocked.CompareExchange(ref _cachedReadAsyncContext, instance, null); - } - - /// - override public Task IsDBNullAsync(int i, CancellationToken cancellationToken) - { - try - { - CheckHeaderIsReady(columnIndex: i); - } - catch (Exception ex) - { - if (!ADP.IsCatchableExceptionType(ex)) - { - throw; - } - return Task.FromException(ex); - } - - // Shortcut - if there are no issues and the data is already read, then just return the value - if ((_sharedState._nextColumnHeaderToRead > i) && (!cancellationToken.IsCancellationRequested) && (_currentTask == null)) - { - var data = _data; - if (data != null) - { - return data[i].IsNull ? ADP.TrueTask : ADP.FalseTask; - } - else - { - // Reader was closed between the CheckHeaderIsReady and accessing _data - throw closed exception - return Task.FromException(ADP.ExceptionWithStackTrace(ADP.DataReaderClosed())); - } - } - else - { - // Throw if there is any current task - if (_currentTask != null) - { - return Task.FromException(ADP.ExceptionWithStackTrace(ADP.AsyncOperationPending())); - } - - // If user's token is canceled, return a canceled task - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - // Shortcut - if we have enough data, then run sync - try - { - if (WillHaveEnoughData(i, headerOnly: true)) - { -#if DEBUG - try - { - _stateObj._shouldHaveEnoughData = true; -#endif - ReadColumnHeader(i); - return _data[i].IsNull ? ADP.TrueTask : ADP.FalseTask; -#if DEBUG - } - finally - { - _stateObj._shouldHaveEnoughData = false; - } -#endif - } - } - catch (Exception ex) - { - if (!ADP.IsCatchableExceptionType(ex)) - { - throw; - } - return Task.FromException(ex); - } - - using (var registrationHolder = new DisposableTemporaryOnStack()) - { - // Setup and check for pending task - TaskCompletionSource source = new TaskCompletionSource(); - Task original = Interlocked.CompareExchange(ref _currentTask, source.Task, null); - if (original != null) - { - source.SetException(ADP.ExceptionWithStackTrace(ADP.AsyncOperationPending())); - return source.Task; - } - - // Check if cancellation due to close is requested (this needs to be done after setting _currentTask) - if (_cancelAsyncOnCloseToken.IsCancellationRequested) - { - source.SetCanceled(); - _currentTask = null; - return source.Task; - } - - // Setup cancellations - if (cancellationToken.CanBeCanceled) - { - registrationHolder.Set(cancellationToken.Register(SqlCommand.s_cancelIgnoreFailure, _command)); - } - - IsDBNullAsyncCallContext context = Interlocked.Exchange(ref _cachedIsDBNullContext, null) ?? new IsDBNullAsyncCallContext(); - - Debug.Assert(context.Reader == null && context.Source == null && context.Disposable == default, "cached ISDBNullAsync context not properly disposed"); - - context.Set(this, source, registrationHolder.Take()); - context._columnIndex = i; - - // Setup async - PrepareAsyncInvocation(useSnapshot: true); - - return InvokeAsyncCall(context); - } - } - } - - private static Task IsDBNullAsyncExecute(Task task, object state) - { - IsDBNullAsyncCallContext context = (IsDBNullAsyncCallContext)state; - SqlDataReader reader = context.Reader; - - if (task != null) - { - reader.PrepareForAsyncContinuation(); - } - - TdsOperationStatus result = reader.TryReadColumnHeader(context._columnIndex); - if (result == TdsOperationStatus.Done) - { - return reader._data[context._columnIndex].IsNull ? ADP.TrueTask : ADP.FalseTask; - } - else - { - return reader.ExecuteAsyncCall(context); - } - } - - private void SetCachedIDBNullAsyncCallContext(IsDBNullAsyncCallContext instance) - { - Interlocked.CompareExchange(ref _cachedIsDBNullContext, instance, null); - } - - /// - override public Task GetFieldValueAsync(int i, CancellationToken cancellationToken) - { - try - { - CheckDataIsReady(columnIndex: i); - - // Shortcut - if there are no issues and the data is already read, then just return the value - if ((!IsCommandBehavior(CommandBehavior.SequentialAccess)) && (_sharedState._nextColumnDataToRead > i) && (!cancellationToken.IsCancellationRequested) && (_currentTask == null)) - { - var data = _data; - var metaData = _metaData; - if ((data != null) && (metaData != null)) - { - return Task.FromResult(GetFieldValueFromSqlBufferInternal(data[i], metaData[i], isAsync: false)); - } - else - { - // Reader was closed between the CheckDataIsReady and accessing _data/_metaData - throw closed exception - return Task.FromException(ADP.ExceptionWithStackTrace(ADP.DataReaderClosed())); - } - } - } - catch (Exception ex) - { - if (!ADP.IsCatchableExceptionType(ex)) - { - throw; - } - return Task.FromException(ex); - } - - // Throw if there is any current task - if (_currentTask != null) - { - return Task.FromException(ADP.ExceptionWithStackTrace(ADP.AsyncOperationPending())); - } - - // If user's token is canceled, return a canceled task - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - // Shortcut - if we have enough data, then run sync - try - { - if (WillHaveEnoughData(i)) - { -#if DEBUG - try - { - _stateObj._shouldHaveEnoughData = true; -#endif - return Task.FromResult(GetFieldValueInternal(i, isAsync: true)); -#if DEBUG - } - finally - { - _stateObj._shouldHaveEnoughData = false; - } -#endif - } - } - catch (Exception ex) - { - if (!ADP.IsCatchableExceptionType(ex)) - { - throw; - } - return Task.FromException(ex); - } - - using (var registrationHolder = new DisposableTemporaryOnStack()) - { - // Setup and check for pending task - TaskCompletionSource source = new TaskCompletionSource(); - Task original = Interlocked.CompareExchange(ref _currentTask, source.Task, null); - if (original != null) - { - source.SetException(ADP.ExceptionWithStackTrace(ADP.AsyncOperationPending())); - return source.Task; - } - - // Check if cancellation due to close is requested (this needs to be done after setting _currentTask) - if (_cancelAsyncOnCloseToken.IsCancellationRequested) - { - source.SetCanceled(); - _currentTask = null; - return source.Task; - } - - // Setup cancellations - if (cancellationToken.CanBeCanceled) - { - registrationHolder.Set(cancellationToken.Register(SqlCommand.s_cancelIgnoreFailure, _command)); - } - - // Setup async - PrepareAsyncInvocation(useSnapshot: true); - - GetFieldValueAsyncCallContext context = new GetFieldValueAsyncCallContext(this, source, registrationHolder.Take()); - context._columnIndex = i; - - return InvokeAsyncCall(context); - } - } - - private static Task GetFieldValueAsyncExecute(Task task, object state) - { - GetFieldValueAsyncCallContext context = (GetFieldValueAsyncCallContext)state; - SqlDataReader reader = context.Reader; - int columnIndex = context._columnIndex; - if (task != null) - { - reader.PrepareForAsyncContinuation(); - } - - if (typeof(T) == typeof(Stream) || typeof(T) == typeof(TextReader) || typeof(T) == typeof(XmlReader)) - { - if (reader.IsCommandBehavior(CommandBehavior.SequentialAccess) && reader._sharedState._dataReady) - { - bool internalReadSuccess = false; - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); - internalReadSuccess = reader.TryReadColumnInternal(context._columnIndex, readHeaderOnly: true, forStreaming: false) == TdsOperationStatus.Done; - } - finally - { - tdsReliabilitySection.Stop(); - } - - if (internalReadSuccess) - { - return Task.FromResult(reader.GetFieldValueFromSqlBufferInternal(reader._data[columnIndex], reader._metaData[columnIndex], isAsync: true)); - } - } - } - - TdsOperationStatus result = reader.TryReadColumn(columnIndex, setTimeout: false); - if (result == TdsOperationStatus.Done) - { - return Task.FromResult(reader.GetFieldValueFromSqlBufferInternal(reader._data[columnIndex], reader._metaData[columnIndex], isAsync: false)); - } - else - { - return reader.ExecuteAsyncCall(context); - } - } - -#if DEBUG - - internal void CompletePendingReadWithSuccess(bool resetForcePendingReadsToWait) - { - var stateObj = _stateObj; - if (stateObj != null) - { - stateObj.CompletePendingReadWithSuccess(resetForcePendingReadsToWait); - } - } - - internal void CompletePendingReadWithFailure(int errorCode, bool resetForcePendingReadsToWait) - { - var stateObj = _stateObj; - if (stateObj != null) - { - stateObj.CompletePendingReadWithFailure(errorCode, resetForcePendingReadsToWait); - } - } - -#endif - - internal abstract class SqlDataReaderBaseAsyncCallContext : AAsyncBaseCallContext - { - internal static readonly Action, object> s_completeCallback = CompleteAsyncCallCallback; - - internal static readonly Func> s_executeCallback = ExecuteAsyncCallCallback; - - protected SqlDataReaderBaseAsyncCallContext() - { - } - - protected SqlDataReaderBaseAsyncCallContext(SqlDataReader owner, TaskCompletionSource source) - { - Set(owner, source); - } - - internal abstract Func> Execute { get; } - - internal SqlDataReader Reader { get => _owner; set => _owner = value; } - - public TaskCompletionSource Source { get => _source; set => _source = value; } - - private static Task ExecuteAsyncCallCallback(Task task, object state) - { - SqlDataReaderBaseAsyncCallContext context = (SqlDataReaderBaseAsyncCallContext)state; - return context.Reader.ContinueAsyncCall(task, context); - } - - private static void CompleteAsyncCallCallback(Task task, object state) - { - SqlDataReaderBaseAsyncCallContext context = (SqlDataReaderBaseAsyncCallContext)state; - context.Reader.CompleteAsyncCall(task, context); - } - } - - internal abstract class SqlDataReaderAsyncCallContext : SqlDataReaderBaseAsyncCallContext - where TDisposable : IDisposable - { - private TDisposable _disposable; - - public TDisposable Disposable { get => _disposable; set => _disposable = value; } - - public void Set(SqlDataReader owner, TaskCompletionSource source, TDisposable disposable) - { - base.Set(owner, source); - _disposable = disposable; - } - - protected override void DisposeCore() - { - TDisposable copy = _disposable; - _disposable = default; - copy?.Dispose(); - } - } - - internal sealed class ReadAsyncCallContext : SqlDataReaderAsyncCallContext - { - internal static readonly Func> s_execute = SqlDataReader.ReadAsyncExecute; - - internal bool _hasMoreData; - internal bool _hasReadRowToken; - - internal ReadAsyncCallContext() - { - } - - internal override Func> Execute => s_execute; - - protected override void AfterCleared(SqlDataReader owner) - { - owner.SetCachedReadAsyncCallContext(this); - } - } - - internal sealed class IsDBNullAsyncCallContext : SqlDataReaderAsyncCallContext - { - internal static readonly Func> s_execute = SqlDataReader.IsDBNullAsyncExecute; - - internal int _columnIndex; - - internal IsDBNullAsyncCallContext() { } - - internal override Func> Execute => s_execute; - - protected override void AfterCleared(SqlDataReader owner) - { - owner.SetCachedIDBNullAsyncCallContext(this); - } - } - - private sealed class HasNextResultAsyncCallContext : SqlDataReaderAsyncCallContext - { - private static readonly Func> s_execute = SqlDataReader.NextResultAsyncExecute; - - public HasNextResultAsyncCallContext(SqlDataReader reader, TaskCompletionSource source, CancellationTokenRegistration disposable) - { - Set(reader, source, disposable); - } - - internal override Func> Execute => s_execute; - } - - private sealed class GetBytesAsyncCallContext : SqlDataReaderAsyncCallContext - { - internal enum OperationMode - { - Seek = 0, - Read = 1 - } - - private static readonly Func> s_executeSeek = SqlDataReader.GetBytesAsyncSeekExecute; - private static readonly Func> s_executeRead = SqlDataReader.GetBytesAsyncReadExecute; - - internal int _columnIndex; - internal byte[] _buffer; - internal int _index; - internal int _length; - internal int _timeout; - internal CancellationToken _cancellationToken; - internal CancellationToken _timeoutToken; - internal int _totalBytesRead; - - internal OperationMode _mode; - - internal GetBytesAsyncCallContext(SqlDataReader reader) - { - Reader = reader ?? throw new ArgumentNullException(nameof(reader)); - } - - internal override Func> Execute => _mode == OperationMode.Seek ? s_executeSeek : s_executeRead; - - protected override void Clear() - { - _buffer = null; - _cancellationToken = default; - _timeoutToken = default; - base.Clear(); - } - } - - private sealed class GetFieldValueAsyncCallContext : SqlDataReaderAsyncCallContext - { - private static readonly Func> s_execute = SqlDataReader.GetFieldValueAsyncExecute; - - internal int _columnIndex; - - internal GetFieldValueAsyncCallContext() { } - - internal GetFieldValueAsyncCallContext(SqlDataReader reader, TaskCompletionSource source, CancellationTokenRegistration disposable) - { - Set(reader, source, disposable); - } - - protected override void Clear() - { - _columnIndex = -1; - base.Clear(); - } - - internal override Func> Execute => s_execute; - } - - /// - /// Starts the process of executing an async call using an SqlDataReaderAsyncCallContext derived context object. - /// After this call the context lifetime is handled by BeginAsyncCall ContinueAsyncCall and CompleteAsyncCall AsyncCall methods - /// - /// - /// - /// - /// - private Task InvokeAsyncCall(SqlDataReaderBaseAsyncCallContext context) - { - TaskCompletionSource source = context.Source; - try - { - Task task; - try - { - task = context.Execute(null, context); - } - catch (Exception ex) - { - task = Task.FromException(ex); - } - - if (task.IsCompleted) - { - CompleteAsyncCall(task, context); - } - else - { - task.ContinueWith( - continuationAction: SqlDataReaderBaseAsyncCallContext.s_completeCallback, - state: context, - TaskScheduler.Default - ); - } - } - catch (AggregateException e) - { - source.TrySetException(e.InnerException); - } - catch (Exception e) - { - source.TrySetException(e); - } - - // Fall through for exceptions/completing async - return source.Task; - } - - /// - /// Begins an async call checking for cancellation and then setting up the callback for when data is available - /// - /// - /// - /// - private Task ExecuteAsyncCall(AAsyncBaseCallContext context) - { - // _networkPacketTaskSource could be null if the connection was closed - // while an async invocation was outstanding. - TaskCompletionSource completionSource = _stateObj._networkPacketTaskSource; - if (_cancelAsyncOnCloseToken.IsCancellationRequested || completionSource == null) - { - // Cancellation requested due to datareader being closed - return Task.FromException(ADP.ExceptionWithStackTrace(ADP.ClosedConnectionError())); - } - else - { - return completionSource.Task.ContinueWith( - continuationFunction: SqlDataReaderBaseAsyncCallContext.s_executeCallback, - state: context, - TaskScheduler.Default - ).Unwrap(); - } - } - - /// - /// When data has become available for an async call it is woken and this method is called. - /// It will call the async execution func and if a Task is returned indicating more data - /// is needed it will wait until it is called again when more is available - /// - /// - /// - /// - /// - private Task ContinueAsyncCall(Task task, SqlDataReaderBaseAsyncCallContext context) - { - // this function must be an instance function called from the static callback because otherwise a compiler error - // is caused by accessing the _cancelAsyncOnCloseToken field of a MarshalByRefObject derived class - if (task.IsFaulted) - { - // Somehow the network task faulted - return the exception - return Task.FromException(task.Exception.InnerException); - } - else if (!_cancelAsyncOnCloseToken.IsCancellationRequested) - { - TdsParserStateObject stateObj = _stateObj; - if (stateObj != null) - { - // protect continuations against concurrent - // close and cancel - lock (stateObj) - { - if (_stateObj != null) - { // reader not closed while we waited for the lock - if (task.IsCanceled) - { - if (_parser != null) - { - _parser.State = TdsParserState.Broken; // We failed to respond to attention, we have to quit! - _parser.Connection.BreakConnection(); - _parser.ThrowExceptionAndWarning(_stateObj); - } - } - else - { - if (!IsClosed) - { - try - { - return context.Execute(task, context); - } - catch (Exception) - { - CleanupAfterAsyncInvocation(); - throw; - } - } - } - } - } - } - } - // if stateObj is null, or we closed the connection or the connection was already closed, - // then mark this operation as cancelled. - return Task.FromException(ADP.ExceptionWithStackTrace(ADP.ClosedConnectionError())); - } - - /// - /// When data has been successfully processed for an async call the async func will call this - /// function to set the result into the task and cleanup the async state ready for another call - /// - /// - /// - /// - private void CompleteAsyncCall(Task task, SqlDataReaderBaseAsyncCallContext context) - { - TaskCompletionSource source = context.Source; - context.Dispose(); - - // If something has forced us to switch to SyncOverAsync mode while in an async task then we need to guarantee that we do the cleanup - // This avoids us replaying non-replayable data (such as DONE or ENV_CHANGE tokens) - var stateObj = _stateObj; - bool ignoreCloseToken = (stateObj != null) && (stateObj._syncOverAsync); - CleanupAfterAsyncInvocation(ignoreCloseToken); - - Task current = Interlocked.CompareExchange(ref _currentTask, null, source.Task); - Debug.Assert(current == source.Task, "Should not be able to change the _currentTask while an asynchronous operation is pending"); - - if (task.IsFaulted) - { - Exception e = task.Exception.InnerException; - source.TrySetException(e); - } - else if (task.IsCanceled) - { - source.TrySetCanceled(); - } - else - { - source.TrySetResult(task.Result); - } - } - - private sealed class Snapshot - { - public bool _dataReady; - public bool _haltRead; - public bool _metaDataConsumed; - public bool _browseModeInfoConsumed; - public bool _hasRows; - public ALTROWSTATUS _altRowStatus; - public int _nextColumnDataToRead; - public int _nextColumnHeaderToRead; - public long _columnDataBytesRead; - public long _columnDataBytesRemaining; - - public _SqlMetaDataSet _metadata; - public _SqlMetaDataSetCollection _altMetaDataSetCollection; - public MultiPartTableName[] _tableNames; - - public SqlSequentialStream _currentStream; - public SqlSequentialTextReader _currentTextReader; - } - - private void PrepareAsyncInvocation(bool useSnapshot) - { - // if there is already a snapshot, then the previous async command - // completed with exception or cancellation. We need to continue - // with the old snapshot. - if (useSnapshot) - { - Debug.Assert(!_stateObj._asyncReadWithoutSnapshot, "Can't prepare async invocation with snapshot if doing async without snapshots"); - - if (_snapshot == null) - { - _snapshot = new Snapshot(); - - _snapshot._dataReady = _sharedState._dataReady; - _snapshot._haltRead = _haltRead; - _snapshot._metaDataConsumed = _metaDataConsumed; - _snapshot._browseModeInfoConsumed = _browseModeInfoConsumed; - _snapshot._hasRows = _hasRows; - _snapshot._altRowStatus = _altRowStatus; - _snapshot._nextColumnDataToRead = _sharedState._nextColumnDataToRead; - _snapshot._nextColumnHeaderToRead = _sharedState._nextColumnHeaderToRead; - _snapshot._columnDataBytesRead = _columnDataBytesRead; - _snapshot._columnDataBytesRemaining = _sharedState._columnDataBytesRemaining; - - // _metadata and _altaMetaDataSetCollection must be Cloned - // before they are updated - _snapshot._metadata = _metaData; - _snapshot._altMetaDataSetCollection = _altMetaDataSetCollection; - _snapshot._tableNames = _tableNames; - - _snapshot._currentStream = _currentStream; - _snapshot._currentTextReader = _currentTextReader; - - _stateObj.SetSnapshot(); - } - } - else - { - Debug.Assert(_snapshot == null, "Can prepare async invocation without snapshot if there is currently a snapshot"); - _stateObj._asyncReadWithoutSnapshot = true; - } - - _stateObj._syncOverAsync = false; - _stateObj._executionContext = ExecutionContext.Capture(); - } - - private void CleanupAfterAsyncInvocation(bool ignoreCloseToken = false) - { - var stateObj = _stateObj; - if (stateObj != null) - { - // If close requested cancellation and we have a snapshot, then it will deal with cleaning up - // NOTE: There are some cases where we wish to ignore the close token, such as when we've read some data that is not replayable (e.g. DONE or ENV_CHANGE token) - if ((ignoreCloseToken) || (!_cancelAsyncOnCloseToken.IsCancellationRequested) || (stateObj._asyncReadWithoutSnapshot)) - { - // Prevent race condition between the DataReader being closed (e.g. when another MARS thread has an error) - lock (stateObj) - { - if (_stateObj != null) - { // reader not closed while we waited for the lock - CleanupAfterAsyncInvocationInternal(_stateObj); - Debug.Assert(_snapshot == null && !_stateObj._asyncReadWithoutSnapshot, "Snapshot not null or async without snapshot still enabled after cleaning async state"); - } - } - } - } - } - - // This function is called directly if calling function already closed the reader, so _stateObj is null, - // in other cases parameterless version should be called - private void CleanupAfterAsyncInvocationInternal(TdsParserStateObject stateObj, bool resetNetworkPacketTaskSource = true) - { - if (resetNetworkPacketTaskSource) - { - stateObj._networkPacketTaskSource = null; - } - stateObj.ResetSnapshot(); - stateObj._syncOverAsync = true; - stateObj._executionContext = null; - stateObj._asyncReadWithoutSnapshot = false; -#if DEBUG - stateObj._permitReplayStackTraceToDiffer = false; -#endif - - // We are setting this to null inside the if-statement because stateObj==null means that the reader hasn't been initialized or has been closed (either way _snapshot should already be null) - _snapshot = null; - } - - private void PrepareForAsyncContinuation() - { - Debug.Assert(((_snapshot != null) || (_stateObj._asyncReadWithoutSnapshot)), "Can not prepare for an async continuation if no async if setup"); - if (_snapshot != null) - { - _sharedState._dataReady = _snapshot._dataReady; - _haltRead = _snapshot._haltRead; - _metaDataConsumed = _snapshot._metaDataConsumed; - _browseModeInfoConsumed = _snapshot._browseModeInfoConsumed; - _hasRows = _snapshot._hasRows; - _altRowStatus = _snapshot._altRowStatus; - _sharedState._nextColumnDataToRead = _snapshot._nextColumnDataToRead; - _sharedState._nextColumnHeaderToRead = _snapshot._nextColumnHeaderToRead; - _columnDataBytesRead = _snapshot._columnDataBytesRead; - _sharedState._columnDataBytesRemaining = _snapshot._columnDataBytesRemaining; - - _metaData = _snapshot._metadata; - _altMetaDataSetCollection = _snapshot._altMetaDataSetCollection; - _tableNames = _snapshot._tableNames; - - _currentStream = _snapshot._currentStream; - _currentTextReader = _snapshot._currentTextReader; - - _stateObj.PrepareReplaySnapshot(); - } - - _stateObj._executionContext = ExecutionContext.Capture(); - } - - private void SwitchToAsyncWithoutSnapshot() - { - Debug.Assert(_snapshot != null, "Should currently have a snapshot"); - Debug.Assert(_stateObj != null && !_stateObj._asyncReadWithoutSnapshot, "Already in async without snapshot"); - - _snapshot = null; - _stateObj.ResetSnapshot(); - _stateObj._asyncReadWithoutSnapshot = true; - } - - }// SqlDataReader -}// namespace diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReaderSmi.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReaderSmi.cs deleted file mode 100644 index e72c240f23..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlDataReaderSmi.cs +++ /dev/null @@ -1,1341 +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.Data; -using System.Data.Common; -using System.Data.SqlTypes; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using Microsoft.Data.Common; -using Microsoft.Data.ProviderBase; -using Microsoft.Data.SqlClient.Server; - - -using Microsoft.Data.SqlTypes; - -namespace Microsoft.Data.SqlClient -{ - // SqlServer provider's implementation of ISqlReader. - // Supports ISqlReader and ISqlResultSet objects. - // - // User should never be able to create one of these themselves, nor subclass. - // This is accomplished by having no public override constructors. - internal sealed class SqlDataReaderSmi : SqlDataReader - { - // - // IDBRecord properties - // - public override int FieldCount - { - get - { - ThrowIfClosed(); - return InternalFieldCount; - } - } - - public override int VisibleFieldCount - { - get - { - ThrowIfClosed(); - - if (FNotInResults()) - { - return 0; - } - - return _visibleColumnCount; - } - } - - // - // IDBRecord Metadata Methods - // - public override string GetName(int ordinal) - { - EnsureCanGetMetaData(); - return _currentMetaData[ordinal].Name; - } - - public override string GetDataTypeName(int ordinal) - { - EnsureCanGetMetaData(); - SmiExtendedMetaData md = _currentMetaData[ordinal]; - if (SqlDbType.Udt == md.SqlDbType) - { - return md.TypeSpecificNamePart1 + "." + md.TypeSpecificNamePart2 + "." + md.TypeSpecificNamePart3; - } - else - { - return md.TypeName; - } - } - - public override Type GetFieldType(int ordinal) - { - EnsureCanGetMetaData(); - if (_currentMetaData[ordinal].SqlDbType == SqlDbType.Udt) - { - return _currentMetaData[ordinal].Type; - } - else - { - return MetaType.GetMetaTypeFromSqlDbType(_currentMetaData[ordinal].SqlDbType, _currentMetaData[ordinal].IsMultiValued).ClassType; - } - } - - override public Type GetProviderSpecificFieldType(int ordinal) - { - EnsureCanGetMetaData(); - - if (SqlDbType.Udt == _currentMetaData[ordinal].SqlDbType) - { - return _currentMetaData[ordinal].Type; - } - else - { - return MetaType.GetMetaTypeFromSqlDbType(_currentMetaData[ordinal].SqlDbType, _currentMetaData[ordinal].IsMultiValued).SqlType; - } - } - - public override int Depth - { - get - { - ThrowIfClosed(); - return 0; - } - } // UNDONE: (alazela 10/14/2001) Multi-level reader not impl. - - public override object GetValue(int ordinal) - { - EnsureCanGetCol(ordinal); - SmiQueryMetaData metaData = _currentMetaData[ordinal]; - if (_currentConnection.Is2008OrNewer) - { - return ValueUtilsSmi.GetValue200(_readerEventSink, (SmiTypedGetterSetter)_currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext); - } - else - { - return ValueUtilsSmi.GetValue(_readerEventSink, _currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext); - } - } - - public override T GetFieldValue(int ordinal) - { - EnsureCanGetCol(ordinal); - SmiQueryMetaData metaData = _currentMetaData[ordinal]; - - if (typeof(INullable).IsAssignableFrom(typeof(T))) - { - // If its a SQL Type or Nullable UDT - if (_currentConnection.Is2008OrNewer) - { - return (T)ValueUtilsSmi.GetSqlValue200(_readerEventSink, (SmiTypedGetterSetter)_currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext); - } - else - { - return (T)ValueUtilsSmi.GetSqlValue(_readerEventSink, _currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext); - } - } - else - { - // Otherwise Its a CLR or non-Nullable UDT - if (_currentConnection.Is2008OrNewer) - { - return (T)ValueUtilsSmi.GetValue200(_readerEventSink, (SmiTypedGetterSetter)_currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext); - } - else - { - return (T)ValueUtilsSmi.GetValue(_readerEventSink, _currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext); - } - } - } - - public override Task GetFieldValueAsync(int ordinal, CancellationToken cancellationToken) - { - // As per Async spec, Context Connections do not support async - return ADP.CreatedTaskWithException(ADP.ExceptionWithStackTrace(SQL.NotAvailableOnContextConnection())); - } - - override internal SqlBuffer.StorageType GetVariantInternalStorageType(int ordinal) - { - Debug.Assert(_currentColumnValuesV3 != null, "Attempting to get variant internal storage type without calling GetValue first"); - if (IsDBNull(ordinal)) - { - return SqlBuffer.StorageType.Empty; - } - - SmiMetaData valueMetaData = _currentColumnValuesV3.GetVariantType(_readerEventSink, ordinal); - if (valueMetaData == null) - { - return SqlBuffer.StorageType.Empty; - } - else - { - return ValueUtilsSmi.SqlDbTypeToStorageType(valueMetaData.SqlDbType); - } - } - - public override int GetValues(object[] values) - { - EnsureCanGetCol(0); - if (values == null) - { - throw ADP.ArgumentNull(nameof(values)); - } - - int copyLength = (values.Length < _visibleColumnCount) ? values.Length : _visibleColumnCount; - for (int i = 0; i < copyLength; i++) - { - values[_indexMap[i]] = GetValue(i); - } - return copyLength; - } - - public override int GetOrdinal(string name) - { - EnsureCanGetMetaData(); - if (_fieldNameLookup == null) - { - _fieldNameLookup = new FieldNameLookup((IDataReader)this, -1); // TODO: Need to support DefaultLCID for name comparisons - } - return _fieldNameLookup.GetOrdinal(name); // MDAC 71470 - } - - // Generic array access by column index (accesses column value) - public override object this[int ordinal] => GetValue(ordinal); - - // Generic array access by column name (accesses column value) - public override object this[string strName] => GetValue(GetOrdinal(strName)); - - // - // IDataRecord Data Access methods - // - public override bool IsDBNull(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.IsDBNull(_readerEventSink, _currentColumnValuesV3, ordinal); - } - - public override Task IsDBNullAsync(int ordinal, CancellationToken cancellationToken) - { - // As per Async spec, Context Connections do not support async - return ADP.CreatedTaskWithException(ADP.ExceptionWithStackTrace(SQL.NotAvailableOnContextConnection())); - } - - public override bool GetBoolean(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetBoolean(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override byte GetByte(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetByte(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override long GetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetBytes(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], fieldOffset, buffer, bufferOffset, length, true); - } - - // XmlReader support code calls this method. - internal override long GetBytesInternal(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetBytesInternal(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], fieldOffset, buffer, bufferOffset, length, false); - } - - public override char GetChar(int ordinal) => throw ADP.NotSupported(); - - public override long GetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) - { - EnsureCanGetCol(ordinal); - SmiExtendedMetaData metaData = _currentMetaData[ordinal]; - if (IsCommandBehavior(CommandBehavior.SequentialAccess)) - { - if (metaData.SqlDbType == SqlDbType.Xml) - { - return GetStreamingXmlChars(ordinal, fieldOffset, buffer, bufferOffset, length); - } - } - return ValueUtilsSmi.GetChars(_readerEventSink, _currentColumnValuesV3, ordinal, metaData, fieldOffset, buffer, bufferOffset, length); - } - - public override Guid GetGuid(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetGuid(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override short GetInt16(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetInt16(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override int GetInt32(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetInt32(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override long GetInt64(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetInt64(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override float GetFloat(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSingle(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override double GetDouble(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetDouble(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override string GetString(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetString(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override decimal GetDecimal(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetDecimal(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override DateTime GetDateTime(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetDateTime(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - // - // IDataReader properties - // - // Logically closed test. I.e. is this object closed as far as external access is concerned? - public override bool IsClosed => IsReallyClosed(); - - public override int RecordsAffected => Command.InternalRecordsAffected; - - // - // IDataReader methods - // - internal override void CloseReaderFromConnection() - { - // Context Connections do not support async - so there is no threading issues with closing from the connection - CloseInternal(closeConnection: false); - } - - public override void Close() - { - // Connection should be open at this point, so we can do multiple checks of HasEvents, and we may need to close the connection afterwards - CloseInternal(closeConnection: IsCommandBehavior(CommandBehavior.CloseConnection)); - } - - private void CloseInternal(bool closeConnection) - { - using (TryEventScope.Create(" {0}", ObjectID)) - { - bool processFinallyBlock = true; - try - { - if (!IsClosed) - { - _hasRows = false; - - // Process the remaining events. This makes sure that environment changes are applied and any errors are picked up. - while (_eventStream.HasEvents) - { - _eventStream.ProcessEvent(_readerEventSink); - _readerEventSink.ProcessMessagesAndThrow(true); - } - - // Close the request executor - _requestExecutor.Close(_readerEventSink); - _readerEventSink.ProcessMessagesAndThrow(true); - } - } - catch (Exception e) - { - processFinallyBlock = ADP.IsCatchableExceptionType(e); - throw; - } - finally - { - if (processFinallyBlock) - { - _isOpen = false; - - if ((closeConnection) && (Connection != null)) - { - Connection.Close(); - } - } - } - } - } - - // Move to the next resultset - public override unsafe bool NextResult() - { - ThrowIfClosed(); - - bool hasAnotherResult = InternalNextResult(false); - - return hasAnotherResult; - } - - public override Task NextResultAsync(CancellationToken cancellationToken) - { - // Async not supported on Context Connections - return ADP.CreatedTaskWithException(ADP.ExceptionWithStackTrace(SQL.NotAvailableOnContextConnection())); - } - - internal unsafe bool InternalNextResult(bool ignoreNonFatalMessages) - { - long scopeID = SqlClientEventSource.Log.TryAdvancedScopeEnterEvent(" {0}", ObjectID); - try - { - _hasRows = false; - - if (PositionState.AfterResults != _currentPosition) - { - // Consume any remaning rows in the current result. - - while (InternalRead(ignoreNonFatalMessages)) - { - // This space intentionally left blank - } - - // reset resultset metadata - it will be created again if there is a pending resultset - ResetResultSet(); - - // Process the events until metadata is found or all of the - // available events have been consumed. If there is another - // result, the metadata for it will be available after the last - // read on the prior result. - - while (_currentMetaData == null && _eventStream.HasEvents) - { - _eventStream.ProcessEvent(_readerEventSink); - _readerEventSink.ProcessMessagesAndThrow(ignoreNonFatalMessages); - } - } - - return PositionState.AfterResults != _currentPosition; - } - finally - { - SqlClientEventSource.Log.TryAdvanceScopeLeave(scopeID); - } - } - - public override bool Read() - { - ThrowIfClosed(); - bool hasAnotherRow = InternalRead(false); - - return hasAnotherRow; - } - - public override Task ReadAsync(CancellationToken cancellationToken) - { - // Async not supported on Context Connections - return ADP.CreatedTaskWithException(ADP.ExceptionWithStackTrace(SQL.NotAvailableOnContextConnection())); - } - - internal unsafe bool InternalRead(bool ignoreNonFatalErrors) - { - long scopeID = SqlClientEventSource.Log.TryAdvancedScopeEnterEvent(" {0}", ObjectID); - try - { - // Don't move unless currently in results. - if (FInResults()) - { - - // Set current row to null so we can see if we get a new one - _currentColumnValues = null; - _currentColumnValuesV3 = null; - - // Reset blobs - if (_currentStream != null) - { - _currentStream.SetClosed(); - _currentStream = null; - } - if (_currentTextReader != null) - { - _currentTextReader.SetClosed(); - _currentTextReader = null; - } - - // NOTE: SQLBUDT #386118 -- may indicate that we want to break this loop when we get a MessagePosted callback, but we can't prove that. - while (_currentColumnValues == null && // Did we find a row? - _currentColumnValuesV3 == null && // Did we find a V3 row? - FInResults() && // Was the batch terminated due to a serious error? - PositionState.AfterRows != _currentPosition && // Have we seen a statement completed event? - _eventStream.HasEvents) - { // Have we processed all events? - _eventStream.ProcessEvent(_readerEventSink); - _readerEventSink.ProcessMessagesAndThrow(ignoreNonFatalErrors); - } - } - - return PositionState.OnRow == _currentPosition; - } - finally - { - SqlClientEventSource.Log.TryAdvanceScopeLeave(scopeID); - } - } - - public override DataTable GetSchemaTable() - { - ThrowIfClosed(); - - if (_schemaTable == null && FInResults()) - { - - DataTable schemaTable = new DataTable("SchemaTable") - { - Locale = System.Globalization.CultureInfo.InvariantCulture, - MinimumCapacity = InternalFieldCount - }; - - DataColumn ColumnName = new DataColumn(SchemaTableColumn.ColumnName, typeof(string)); - DataColumn Ordinal = new DataColumn(SchemaTableColumn.ColumnOrdinal, typeof(int)); - DataColumn Size = new DataColumn(SchemaTableColumn.ColumnSize, typeof(int)); - DataColumn Precision = new DataColumn(SchemaTableColumn.NumericPrecision, typeof(short)); - DataColumn Scale = new DataColumn(SchemaTableColumn.NumericScale, typeof(short)); - - DataColumn DataType = new DataColumn(SchemaTableColumn.DataType, typeof(Type)); - DataColumn ProviderSpecificDataType = new DataColumn(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(Type)); - DataColumn ProviderType = new DataColumn(SchemaTableColumn.ProviderType, typeof(int)); - DataColumn NonVersionedProviderType = new DataColumn(SchemaTableColumn.NonVersionedProviderType, typeof(int)); - - DataColumn IsLong = new DataColumn(SchemaTableColumn.IsLong, typeof(bool)); - DataColumn AllowDBNull = new DataColumn(SchemaTableColumn.AllowDBNull, typeof(bool)); - DataColumn IsReadOnly = new DataColumn(SchemaTableOptionalColumn.IsReadOnly, typeof(bool)); - DataColumn IsRowVersion = new DataColumn(SchemaTableOptionalColumn.IsRowVersion, typeof(bool)); - - DataColumn IsUnique = new DataColumn(SchemaTableColumn.IsUnique, typeof(bool)); - DataColumn IsKey = new DataColumn(SchemaTableColumn.IsKey, typeof(bool)); - DataColumn IsAutoIncrement = new DataColumn(SchemaTableOptionalColumn.IsAutoIncrement, typeof(bool)); - DataColumn IsHidden = new DataColumn(SchemaTableOptionalColumn.IsHidden, typeof(bool)); - - DataColumn BaseCatalogName = new DataColumn(SchemaTableOptionalColumn.BaseCatalogName, typeof(string)); - DataColumn BaseSchemaName = new DataColumn(SchemaTableColumn.BaseSchemaName, typeof(string)); - DataColumn BaseTableName = new DataColumn(SchemaTableColumn.BaseTableName, typeof(string)); - DataColumn BaseColumnName = new DataColumn(SchemaTableColumn.BaseColumnName, typeof(string)); - - // unique to SqlClient - DataColumn BaseServerName = new DataColumn(SchemaTableOptionalColumn.BaseServerName, typeof(string)); - DataColumn IsAliased = new DataColumn(SchemaTableColumn.IsAliased, typeof(bool)); - DataColumn IsExpression = new DataColumn(SchemaTableColumn.IsExpression, typeof(bool)); - DataColumn IsIdentity = new DataColumn("IsIdentity", typeof(bool)); - // UDT specific. Holds UDT typename ONLY if the type of the column is UDT, otherwise the data type - DataColumn DataTypeName = new DataColumn("DataTypeName", typeof(string)); - DataColumn UdtAssemblyQualifiedName = new DataColumn("UdtAssemblyQualifiedName", typeof(string)); - // Xml metadata specific - DataColumn XmlSchemaCollectionDatabase = new DataColumn("XmlSchemaCollectionDatabase", typeof(string)); - DataColumn XmlSchemaCollectionOwningSchema = new DataColumn("XmlSchemaCollectionOwningSchema", typeof(string)); - DataColumn XmlSchemaCollectionName = new DataColumn("XmlSchemaCollectionName", typeof(string)); - // SparseColumnSet - DataColumn IsColumnSet = new DataColumn("IsColumnSet", typeof(bool)); - - Ordinal.DefaultValue = 0; - IsLong.DefaultValue = false; - - DataColumnCollection columns = schemaTable.Columns; - - // must maintain order for backward compatibility - columns.Add(ColumnName); - columns.Add(Ordinal); - columns.Add(Size); - columns.Add(Precision); - columns.Add(Scale); - columns.Add(IsUnique); - columns.Add(IsKey); - columns.Add(BaseServerName); - columns.Add(BaseCatalogName); - columns.Add(BaseColumnName); - columns.Add(BaseSchemaName); - columns.Add(BaseTableName); - columns.Add(DataType); - columns.Add(AllowDBNull); - columns.Add(ProviderType); - columns.Add(IsAliased); - columns.Add(IsExpression); - columns.Add(IsIdentity); - columns.Add(IsAutoIncrement); - columns.Add(IsRowVersion); - columns.Add(IsHidden); - columns.Add(IsLong); - columns.Add(IsReadOnly); - columns.Add(ProviderSpecificDataType); - columns.Add(DataTypeName); - columns.Add(XmlSchemaCollectionDatabase); - columns.Add(XmlSchemaCollectionOwningSchema); - columns.Add(XmlSchemaCollectionName); - columns.Add(UdtAssemblyQualifiedName); - columns.Add(NonVersionedProviderType); - columns.Add(IsColumnSet); - - for (int i = 0; i < InternalFieldCount; i++) - { - SmiQueryMetaData colMetaData = _currentMetaData[i]; - - long maxLength = colMetaData.MaxLength; - - MetaType metaType = MetaType.GetMetaTypeFromSqlDbType(colMetaData.SqlDbType, colMetaData.IsMultiValued); - if (SmiMetaData.UnlimitedMaxLengthIndicator == maxLength) - { - metaType = MetaType.GetMaxMetaTypeFromMetaType(metaType); - maxLength = (metaType.IsSizeInCharacters && !metaType.IsPlp) ? (0x7fffffff / 2) : 0x7fffffff; - } - - DataRow schemaRow = schemaTable.NewRow(); - - // NOTE: there is an impedence mismatch here - the server always - // treats numeric data as variable length and sends a maxLength - // based upon the precision, whereas TDS always sends 17 for - // the max length; rather than push this logic into the server, - // I've elected to make a fixup here instead. - if (SqlDbType.Decimal == colMetaData.SqlDbType) - { - // TODO: Consider moving this into SmiMetaData itself... - maxLength = TdsEnums.MAX_NUMERIC_LEN; // SQLBUDT 339686 - } - else if (SqlDbType.Variant == colMetaData.SqlDbType) - { - // TODO: Consider moving this into SmiMetaData itself... - maxLength = 8009; // SQLBUDT 340726 - } - - schemaRow[ColumnName] = colMetaData.Name; - schemaRow[Ordinal] = i; - schemaRow[Size] = maxLength; - - schemaRow[ProviderType] = (int)colMetaData.SqlDbType; // SqlDbType - schemaRow[NonVersionedProviderType] = (int)colMetaData.SqlDbType; // SqlDbType - - if (colMetaData.SqlDbType != SqlDbType.Udt) - { - schemaRow[DataType] = metaType.ClassType; // com+ type - schemaRow[ProviderSpecificDataType] = metaType.SqlType; - } - else - { - schemaRow[UdtAssemblyQualifiedName] = colMetaData.Type.AssemblyQualifiedName; - schemaRow[DataType] = colMetaData.Type; - schemaRow[ProviderSpecificDataType] = colMetaData.Type; - } - - // NOTE: there is also an impedence mismatch here - the server - // has different ideas about what the precision value should be - // than does the client bits. I tried fixing up the default - // meta data values in SmiMetaData, however, it caused the - // server suites to fall over dead. Rather than attempt to - // bake it into the server, I'm fixing it up in the client. - byte precision; // default for everything, except certain numeric types. - - // TODO: Consider moving this into SmiMetaData itself... - switch (colMetaData.SqlDbType) - { - case SqlDbType.BigInt: - case SqlDbType.DateTime: - case SqlDbType.Decimal: - case SqlDbType.Int: - case SqlDbType.Money: - case SqlDbType.SmallDateTime: - case SqlDbType.SmallInt: - case SqlDbType.SmallMoney: - case SqlDbType.TinyInt: - precision = colMetaData.Precision; - break; - case SqlDbType.Float: - precision = 15; - break; - case SqlDbType.Real: - precision = 7; - break; - default: - precision = 0xff; // everything else is unknown; - break; - } - - schemaRow[Precision] = precision; - - // TODO: Consider moving this to a utitlity class if we end up with a bunch more of this stuff... - if (SqlDbType.Decimal == colMetaData.SqlDbType || - SqlDbType.Time == colMetaData.SqlDbType || - SqlDbType.DateTime2 == colMetaData.SqlDbType || - SqlDbType.DateTimeOffset == colMetaData.SqlDbType) - { - schemaRow[Scale] = colMetaData.Scale; - } - else - { - schemaRow[Scale] = MetaType.GetMetaTypeFromSqlDbType(colMetaData.SqlDbType, colMetaData.IsMultiValued).Scale; - } - - schemaRow[AllowDBNull] = colMetaData.AllowsDBNull; - if (!(colMetaData.IsAliased.IsNull)) - { - schemaRow[IsAliased] = colMetaData.IsAliased.Value; - } - - if (!(colMetaData.IsKey.IsNull)) - { - schemaRow[IsKey] = colMetaData.IsKey.Value; - } - - if (!(colMetaData.IsHidden.IsNull)) - { - schemaRow[IsHidden] = colMetaData.IsHidden.Value; - } - - if (!(colMetaData.IsExpression.IsNull)) - { - schemaRow[IsExpression] = colMetaData.IsExpression.Value; - } - - schemaRow[IsReadOnly] = colMetaData.IsReadOnly; - schemaRow[IsIdentity] = colMetaData.IsIdentity; - schemaRow[IsColumnSet] = colMetaData.IsColumnSet; - schemaRow[IsAutoIncrement] = colMetaData.IsIdentity; - schemaRow[IsLong] = metaType.IsLong; - - // mark unique for timestamp columns - if (SqlDbType.Timestamp == colMetaData.SqlDbType) - { - schemaRow[IsUnique] = true; - schemaRow[IsRowVersion] = true; - } - else - { - schemaRow[IsUnique] = false; - schemaRow[IsRowVersion] = false; - } - - if (!ADP.IsEmpty(colMetaData.ColumnName)) - { - schemaRow[BaseColumnName] = colMetaData.ColumnName; - } - else if (!ADP.IsEmpty(colMetaData.Name)) - { - // Use projection name if base column name is not present - schemaRow[BaseColumnName] = colMetaData.Name; - } - - if (!ADP.IsEmpty(colMetaData.TableName)) - { - schemaRow[BaseTableName] = colMetaData.TableName; - } - - if (!ADP.IsEmpty(colMetaData.SchemaName)) - { - schemaRow[BaseSchemaName] = colMetaData.SchemaName; - } - - if (!ADP.IsEmpty(colMetaData.CatalogName)) - { - schemaRow[BaseCatalogName] = colMetaData.CatalogName; - } - - if (!ADP.IsEmpty(colMetaData.ServerName)) - { - schemaRow[BaseServerName] = colMetaData.ServerName; - } - - if (SqlDbType.Udt == colMetaData.SqlDbType) - { - schemaRow[DataTypeName] = colMetaData.TypeSpecificNamePart1 + "." + colMetaData.TypeSpecificNamePart2 + "." + colMetaData.TypeSpecificNamePart3; - } - else - { - schemaRow[DataTypeName] = metaType.TypeName; - } - - // Add Xml metadata - if (SqlDbType.Xml == colMetaData.SqlDbType) - { - schemaRow[XmlSchemaCollectionDatabase] = colMetaData.TypeSpecificNamePart1; - schemaRow[XmlSchemaCollectionOwningSchema] = colMetaData.TypeSpecificNamePart2; - schemaRow[XmlSchemaCollectionName] = colMetaData.TypeSpecificNamePart3; - } - - schemaTable.Rows.Add(schemaRow); - schemaRow.AcceptChanges(); - } - - // mark all columns as readonly - foreach (DataColumn column in columns) - { - column.ReadOnly = true; // MDAC 70943 - } - - _schemaTable = schemaTable; - } - - return _schemaTable; - } - - // - // ISqlRecord methods - // - public override SqlBinary GetSqlBinary(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSqlBinary(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override SqlBoolean GetSqlBoolean(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSqlBoolean(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override SqlByte GetSqlByte(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSqlByte(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override SqlInt16 GetSqlInt16(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSqlInt16(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override SqlInt32 GetSqlInt32(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSqlInt32(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override SqlInt64 GetSqlInt64(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSqlInt64(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override SqlSingle GetSqlSingle(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSqlSingle(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override SqlDouble GetSqlDouble(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSqlDouble(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override SqlMoney GetSqlMoney(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSqlMoney(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override SqlDateTime GetSqlDateTime(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSqlDateTime(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - - public override SqlDecimal GetSqlDecimal(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSqlDecimal(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override SqlString GetSqlString(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSqlString(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override SqlGuid GetSqlGuid(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSqlGuid(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal]); - } - - public override SqlChars GetSqlChars(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSqlChars(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], _currentConnection.InternalContext); - } - - public override SqlBytes GetSqlBytes(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSqlBytes(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], _currentConnection.InternalContext); - } - - public override SqlXml GetSqlXml(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetSqlXml(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], _currentConnection.InternalContext); - } - - public override TimeSpan GetTimeSpan(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetTimeSpan(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], _currentConnection.Is2008OrNewer); - } - - public override DateTimeOffset GetDateTimeOffset(int ordinal) - { - EnsureCanGetCol(ordinal); - return ValueUtilsSmi.GetDateTimeOffset(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], _currentConnection.Is2008OrNewer); - } - - public override object GetSqlValue(int ordinal) - { - EnsureCanGetCol(ordinal); - - SmiMetaData metaData = _currentMetaData[ordinal]; - if (_currentConnection.Is2008OrNewer) - { - return ValueUtilsSmi.GetSqlValue200(_readerEventSink, (SmiTypedGetterSetter)_currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext); - } - return ValueUtilsSmi.GetSqlValue(_readerEventSink, _currentColumnValuesV3, ordinal, metaData, _currentConnection.InternalContext); - ; - } - - public override int GetSqlValues(object[] values) - { - EnsureCanGetCol(0); - - if (values == null) - { - throw ADP.ArgumentNull(nameof(values)); - } - - int copyLength = (values.Length < _visibleColumnCount) ? values.Length : _visibleColumnCount; - for (int i = 0; i < copyLength; i++) - { - values[_indexMap[i]] = GetSqlValue(i); - } - - return copyLength; - } - - // - // ISqlReader methods/properties - // - public override bool HasRows - { - get { return _hasRows; } - } - - // - // SqlDataReader method/properties - // - public override Stream GetStream(int ordinal) - { - EnsureCanGetCol(ordinal); - - SmiQueryMetaData metaData = _currentMetaData[ordinal]; - - // For non-null, non-variant types with sequential access, we support proper streaming - if ((metaData.SqlDbType != SqlDbType.Variant) && (IsCommandBehavior(CommandBehavior.SequentialAccess)) && (!ValueUtilsSmi.IsDBNull(_readerEventSink, _currentColumnValuesV3, ordinal))) - { - if (HasActiveStreamOrTextReaderOnColumn(ordinal)) - { - throw ADP.NonSequentialColumnAccess(ordinal, ordinal + 1); - } - _currentStream = ValueUtilsSmi.GetSequentialStream(_readerEventSink, _currentColumnValuesV3, ordinal, metaData); - return _currentStream; - } - else - { - return ValueUtilsSmi.GetStream(_readerEventSink, _currentColumnValuesV3, ordinal, metaData); - } - } - - public override TextReader GetTextReader(int ordinal) - { - EnsureCanGetCol(ordinal); - - SmiQueryMetaData metaData = _currentMetaData[ordinal]; - - // For non-variant types with sequential access, we support proper streaming - if ((metaData.SqlDbType != SqlDbType.Variant) && (IsCommandBehavior(CommandBehavior.SequentialAccess)) && (!ValueUtilsSmi.IsDBNull(_readerEventSink, _currentColumnValuesV3, ordinal))) - { - if (HasActiveStreamOrTextReaderOnColumn(ordinal)) - { - throw ADP.NonSequentialColumnAccess(ordinal, ordinal + 1); - } - _currentTextReader = ValueUtilsSmi.GetSequentialTextReader(_readerEventSink, _currentColumnValuesV3, ordinal, metaData); - return _currentTextReader; - } - else - { - return ValueUtilsSmi.GetTextReader(_readerEventSink, _currentColumnValuesV3, ordinal, metaData); - } - } - - public override XmlReader GetXmlReader(int ordinal) - { - // NOTE: sql_variant can not contain a XML data type: http://msdn.microsoft.com/en-us/library/ms173829.aspx - - EnsureCanGetCol(ordinal); - if (_currentMetaData[ordinal].SqlDbType != SqlDbType.Xml) - { - throw ADP.InvalidCast(); - } - - Stream stream; - if ((IsCommandBehavior(CommandBehavior.SequentialAccess)) && (!ValueUtilsSmi.IsDBNull(_readerEventSink, _currentColumnValuesV3, ordinal))) - { - if (HasActiveStreamOrTextReaderOnColumn(ordinal)) - { - throw ADP.NonSequentialColumnAccess(ordinal, ordinal + 1); - } - // Need to bypass the type check since streams are not usually allowed on XML types - _currentStream = ValueUtilsSmi.GetSequentialStream(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], bypassTypeCheck: true); - stream = _currentStream; - } - else - { - stream = ValueUtilsSmi.GetStream(_readerEventSink, _currentColumnValuesV3, ordinal, _currentMetaData[ordinal], bypassTypeCheck: true); - } - - return SqlTypeWorkarounds.SqlXmlCreateSqlXmlReader(stream, closeInput: false, async: false); - } - - // - // Internal reader state - // - - // Logical state of reader/resultset as viewed by the client - // Does not necessarily match up with server state. - internal enum PositionState - { - BeforeResults, // Before all resultset in request - BeforeRows, // Before all rows in current resultset - OnRow, // On a valid row in the current resultset - AfterRows, // After all rows in current resultset - AfterResults // After all resultsets in request - } - - private PositionState _currentPosition; // Where is the reader relative to incoming results? - private bool _isOpen; // Is the reader open? - private SmiQueryMetaData[] _currentMetaData; // Metadata for current resultset - private int[] _indexMap; // map of indices for visible column - private int _visibleColumnCount; // number of visible columns - private DataTable _schemaTable; // Cache of user-visible extended metadata while in results. - private ITypedGetters _currentColumnValues; // Unmanaged-managed data marshalers/cache - private ITypedGettersV3 _currentColumnValuesV3; // Unmanaged-managed data marshalers/cache for SMI V3 - private bool _hasRows; // Are there any rows in the current resultset? Must be able to say before moving to first row. - private SmiEventStream _eventStream; // The event buffer that receives the events from the execution engine. - private SmiRequestExecutor _requestExecutor; // The used to request actions from the execution engine. - private SqlInternalConnectionSmi _currentConnection; - private ReaderEventSink _readerEventSink; // The event sink that will process events from the event buffer. - private FieldNameLookup _fieldNameLookup; // cached lookup object to improve access time based on field name - private SqlSequentialStreamSmi _currentStream; // The stream on the current column (if any) - private SqlSequentialTextReaderSmi _currentTextReader; // The text reader on the current column (if any) - - // Assumes that if there were any results, the first chunk of them are in the data stream - // (up to the first actual row or the end of the resultsets). - unsafe internal SqlDataReaderSmi( - SmiEventStream eventStream, // the event stream that receives the events from the execution engine - SqlCommand parent, // command that owns reader - CommandBehavior behavior, // behavior specified for this execution - SqlInternalConnectionSmi connection, // connection that owns everybody - SmiEventSink parentSink, // Event sink of parent command - SmiRequestExecutor requestExecutor - ) : base(parent, behavior) - { // UNDONE: handle other command behaviors - _eventStream = eventStream; - _currentConnection = connection; - _readerEventSink = new ReaderEventSink(this, parentSink); - _currentPosition = PositionState.BeforeResults; - _isOpen = true; - _indexMap = null; - _visibleColumnCount = 0; - _currentStream = null; - _currentTextReader = null; - _requestExecutor = requestExecutor; - } - - internal override SmiExtendedMetaData[] GetInternalSmiMetaData() - { - if (_currentMetaData == null || _visibleColumnCount == InternalFieldCount) - { - return _currentMetaData; - } - else - { -#if DEBUG - // DEVNOTE: Interpretation of returned array currently depends on hidden columns - // always appearing at the end, since there currently is no access to the index map - // outside of this class. In Debug code, we check this assumption. - bool sawHiddenColumn = false; -#endif - SmiExtendedMetaData[] visibleMetaData = new SmiExtendedMetaData[_visibleColumnCount]; - for (int i = 0; i < _visibleColumnCount; i++) - { -#if DEBUG - if (_currentMetaData[_indexMap[i]].IsHidden.IsTrue) - { - sawHiddenColumn = true; - } - else - { - Debug.Assert(!sawHiddenColumn); - } -#endif - visibleMetaData[i] = _currentMetaData[_indexMap[i]]; - } - - return visibleMetaData; - } - } - - internal override int GetLocaleId(int ordinal) - { - EnsureCanGetMetaData(); - return (int)_currentMetaData[ordinal].LocaleId; - } - private int InternalFieldCount - { - get - { - if (FNotInResults()) - { - return 0; - } - else - { - return _currentMetaData.Length; - } - } - } - - // Have we cleaned up internal resources? - private bool IsReallyClosed() => !_isOpen; - - // Central checkpoint for closed recordset. - // Any code that requires an open recordset should call this method first! - // Especially any code that accesses unmanaged memory structures whose lifetime - // matches the lifetime of the unmanaged recordset. - internal void ThrowIfClosed([CallerMemberName] string operationName = null) - { - if (IsClosed) - { - throw ADP.DataReaderClosed(operationName); - } - } - - // Central checkpoint to ensure the requested column can be accessed. - // Calling this function serves to notify that it has been accessed by the user. - private void EnsureCanGetCol(int ordinal, [CallerMemberName] string operationName = null) - { - EnsureOnRow(operationName); - } - - internal void EnsureOnRow(string operationName) - { - ThrowIfClosed(operationName); - if (_currentPosition != PositionState.OnRow) - { - throw SQL.InvalidRead(); - } - } - - internal void EnsureCanGetMetaData([CallerMemberName] string operationName = null) - { - ThrowIfClosed(operationName); - if (FNotInResults()) - { - throw SQL.InvalidRead(); // UNDONE: Shouldn't this be a bit more descriptive? - } - } - - private bool FInResults() - { - return !FNotInResults(); - } - - private bool FNotInResults() - { - return (PositionState.AfterResults == _currentPosition || PositionState.BeforeResults == _currentPosition); - } - - private void MetaDataAvailable(SmiQueryMetaData[] md, bool nextEventIsRow) - { - Debug.Assert(_currentPosition != PositionState.AfterResults); - - _currentMetaData = md; - _hasRows = nextEventIsRow; - _fieldNameLookup = null; - _schemaTable = null; // will be rebuilt based on new metadata - _currentPosition = PositionState.BeforeRows; - - // calculate visible column indices - _indexMap = new int[_currentMetaData.Length]; - int i; - int visibleCount = 0; - for (i = 0; i < _currentMetaData.Length; i++) - { - if (!_currentMetaData[i].IsHidden.IsTrue) - { - _indexMap[visibleCount] = i; - visibleCount++; - } - } - _visibleColumnCount = visibleCount; - } - - private bool HasActiveStreamOrTextReaderOnColumn(int columnIndex) - { - bool active = false; - - active |= (_currentStream != null) && (_currentStream.ColumnIndex == columnIndex); - active |= (_currentTextReader != null) && (_currentTextReader.ColumnIndex == columnIndex); - - return active; - } - - // Obsolete V2- method - private void RowAvailable(ITypedGetters row) - { - Debug.Assert(_currentPosition != PositionState.AfterResults); - - _currentColumnValues = row; - _currentPosition = PositionState.OnRow; - } - - private void RowAvailable(ITypedGettersV3 row) - { - Debug.Assert(_currentPosition != PositionState.AfterResults); - - _currentColumnValuesV3 = row; - _currentPosition = PositionState.OnRow; - } - - private void StatementCompleted() - { - Debug.Assert(_currentPosition != PositionState.AfterResults); - - _currentPosition = PositionState.AfterRows; - } - - private void ResetResultSet() - { - _currentMetaData = null; - _visibleColumnCount = 0; - _schemaTable = null; - } - - private void BatchCompleted() - { - Debug.Assert(_currentPosition != PositionState.AfterResults); - - ResetResultSet(); - - _currentPosition = PositionState.AfterResults; - _eventStream.Close(_readerEventSink); - } - - // An implementation of the IEventSink interface that either performs - // the required enviornment changes or forwards the events on to the - // corresponding reader instance. Having the event sink be a separate - // class keeps the IEventSink methods out of SqlDataReader's inteface. - - private sealed class ReaderEventSink : SmiEventSink_Default - { - private readonly SqlDataReaderSmi _reader; - - internal ReaderEventSink(SqlDataReaderSmi reader, SmiEventSink parent) - : base(parent) - { - _reader = reader; - } - - internal override void MetaDataAvailable(SmiQueryMetaData[] md, bool nextEventIsRow) - { - var mdLength = (md != null) ? md.Length : -1; - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, md.Length={1} nextEventIsRow={2}.", _reader.ObjectID, mdLength, nextEventIsRow); - - if (SqlClientEventSource.Log.IsAdvancedTraceOn()) - { - if (md != null) - { - for (int i = 0; i < md.Length; i++) - { - SqlClientEventSource.Log.TraceEvent(" {0}, metaData[{1}] is {2}{3}", _reader.ObjectID, i, md[i].GetType(), md[i].TraceString()); - } - } - } - _reader.MetaDataAvailable(md, nextEventIsRow); - } - - // Obsolete V2- method - internal override void RowAvailable(ITypedGetters row) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} (v2).", _reader.ObjectID); - _reader.RowAvailable(row); - } - - internal override void RowAvailable(ITypedGettersV3 row) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} (ITypedGettersV3).", _reader.ObjectID); - _reader.RowAvailable(row); - } - - internal override void RowAvailable(SmiTypedGetterSetter rowData) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} (SmiTypedGetterSetter).", _reader.ObjectID); - _reader.RowAvailable(rowData); - } - - internal override void StatementCompleted(int recordsAffected) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} recordsAffected= {1}.", _reader.ObjectID, recordsAffected); - - // devnote: relies on SmiEventSink_Default to pass event to parent - // Both command and reader care about StatementCompleted, but for different reasons. - base.StatementCompleted(recordsAffected); - _reader.StatementCompleted(); - } - - internal override void BatchCompleted() - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}.", _reader.ObjectID); - - // devnote: relies on SmiEventSink_Default to pass event to parent - // parent's callback *MUST* come before reader's BatchCompleted, since - // reader will close the event stream during this call, and parent wants - // to extract parameter values before that happens. - base.BatchCompleted(); - _reader.BatchCompleted(); - } - } - - } -} - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionSmi.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionSmi.cs deleted file mode 100644 index 8a164eba37..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionSmi.cs +++ /dev/null @@ -1,578 +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.Data; -using System.Data.Common; -using System.Diagnostics; -using Microsoft.Data.Common; -using Microsoft.Data.SqlClient.Server; -using System.Transactions; - -namespace Microsoft.Data.SqlClient -{ - sealed internal class SqlInternalConnectionSmi : SqlInternalConnection - { - - private SmiContext _smiContext; - private SmiConnection _smiConnection; - private SmiEventSink_Default _smiEventSink; - private int _isInUse; // 1 = Connected to open outer connection, 0 = not connected - - private SqlInternalTransaction _pendingTransaction; // transaction awaiting event signalling that it is active - private SqlInternalTransaction _currentTransaction; // currently active non-context transaction. - - sealed private class EventSink : SmiEventSink_Default - { - - SqlInternalConnectionSmi _connection; - - override internal string ServerVersion - { - get - { - return SmiContextFactory.Instance.ServerVersion; - } - } - - override protected void DispatchMessages(bool ignoreNonFatalMessages) - { - // Override this on the Connection event sink, since we can deal - // with info messages here. - SqlException exception = ProcessMessages(false, ignoreNonFatalMessages); - - if (exception != null) - { - // SQLBUVSTS 225982, query for connection once to avoid race condition between GC (that may collect the connection) and the user thread - SqlConnection connection = _connection.Connection; - if (connection != null && connection.FireInfoMessageEventOnUserErrors) - { - connection.OnInfoMessage(new SqlInfoMessageEventArgs(exception)); - } - else - { - _connection.OnError(exception, false); // we can't really ever break the direct connection, can we? - } - } - } - - internal EventSink(SqlInternalConnectionSmi connection) - { - Debug.Assert(connection != null, "null connection?"); - _connection = connection; - } - - internal override void DefaultDatabaseChanged(string databaseName) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, databaseName='{1}'.", _connection.ObjectID, databaseName); - _connection.CurrentDatabase = databaseName; - } - - internal override void TransactionCommitted(long transactionId) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, transactionId=0x{1}.", _connection.ObjectID, transactionId); - _connection.TransactionEnded(transactionId, TransactionState.Committed); - } - - internal override void TransactionDefected(long transactionId) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, transactionId=0x{1}.", _connection.ObjectID, transactionId); - _connection.TransactionEnded(transactionId, TransactionState.Unknown); - } - - internal override void TransactionEnlisted(long transactionId) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, transactionId=0x{1}.", _connection.ObjectID, transactionId); - _connection.TransactionStarted(transactionId, true); // distributed; - } - - internal override void TransactionEnded(long transactionId) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, transactionId=0x{1}.", _connection.ObjectID, transactionId); - _connection.TransactionEndedByServer(transactionId, TransactionState.Unknown); - } - - internal override void TransactionRolledBack(long transactionId) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, transactionId=0x{1}.", _connection.ObjectID, transactionId); - - // Dev11 1066: ensure delegated transaction is rolled back - _connection.TransactionEndedByServer(transactionId, TransactionState.Aborted); - } - - internal override void TransactionStarted(long transactionId) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, transactionId=0x{1}.", _connection.ObjectID, transactionId); - _connection.TransactionStarted(transactionId, false); // not distributed; - } - } - - internal SqlInternalConnectionSmi(SqlConnectionString connectionOptions, SmiContext smiContext) : base(connectionOptions) - { - Debug.Assert(smiContext != null, "null smiContext?"); - - _smiContext = smiContext; - _smiContext.OutOfScope += new EventHandler(OnOutOfScope); - - _smiConnection = _smiContext.ContextConnection; - Debug.Assert(_smiConnection != null, "null SmiContext.ContextConnection?"); - - _smiEventSink = new EventSink(this); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, constructed new SMI internal connection", ObjectID); - } - - internal SmiContext InternalContext - { - get - { - return _smiContext; - } - } - - internal SmiConnection SmiConnection - { - get - { - return _smiConnection; - } - } - - internal SmiEventSink CurrentEventSink - { - get - { - return _smiEventSink; - } - } - - override internal SqlInternalTransaction CurrentTransaction - { - get - { - return _currentTransaction; - } - } - - override internal bool IsLockedForBulkCopy - { - get - { - return false; // no bulk copy in the Direct connection case. - } - } - - override internal bool Is2000 - { - get - { - return false; // Can't be direct connecting to 2000. - } - } - - override internal bool Is2005OrNewer - { - get - { - return true; // Must be direct connecting to 2005 or newer. - } - } - - override internal bool Is2008OrNewer - { - get - { - return SmiContextFactory.Instance.NegotiatedSmiVersion >= SmiContextFactory.Sql2008Version; - } - } - - override internal SqlInternalTransaction PendingTransaction - { - get - { - return CurrentTransaction; // there are no differences between pending and current in proc. - } - } - - override public string ServerVersion - { - get - { - return SmiContextFactory.Instance.ServerVersion; - } - } - - /// - /// Get boolean that specifies whether an enlisted transaction can be unbound from - /// the connection when that transaction completes. - /// - /// - /// True if the connection string property "TransactionBinding" is set to TransactionBindingEnum.ImplicitUnbind; - /// otherwise, false. - /// - protected override bool UnbindOnTransactionCompletion - { - get - { - return ConnectionOptions.TransactionBinding == SqlConnectionString.TransactionBindingEnum.ImplicitUnbind; - } - } - - // Workaround to access context transaction without rewriting connection pool & internalconnections properly. - // Context transactions SHOULD be considered enlisted. - // This works for now only because we can't unenlist from the context transaction - // DON'T START USING THIS ANYWHERE EXCEPT IN InternalTransaction and in InternalConnectionSmi!!! - private Transaction ContextTransaction - { - get; - set; - } - - private Transaction InternalEnlistedTransaction - { - get - { - // Workaround to access context transaction without rewriting connection pool & internalconnections properly. - // This SHOULD be a simple wrapper around EnlistedTransaction. - // This works for now only because we can't unenlist from the context transaction - Transaction tx = EnlistedTransaction; - - if (tx == null) - { - tx = ContextTransaction; - } - - return tx; - } - } - - override protected void Activate(Transaction transaction) - { - Debug.Assert(false, "Activating an internal SMI connection?"); // we should never be activating, because that would indicate we're being pooled. - } - - internal void Activate() - { - int wasInUse = System.Threading.Interlocked.Exchange(ref _isInUse, 1); - if (0 != wasInUse) - { - throw SQL.ContextConnectionIsInUse(); - } - - CurrentDatabase = _smiConnection.GetCurrentDatabase(_smiEventSink); - - _smiEventSink.ProcessMessagesAndThrow(); - } - - internal void AutomaticEnlistment() - { - Transaction currentSystemTransaction = ADP.GetCurrentTransaction(); // NOTE: Must be first to ensure _smiContext.ContextTransaction is set! - Transaction contextTransaction = _smiContext.ContextTransaction; // returns the transaction that was handed to SysTx that wraps the ContextTransactionId. - long contextTransactionId = _smiContext.ContextTransactionId; - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, contextTransactionId=0x{1}, contextTransaction={2}, currentSystemTransaction={3}.", - ObjectID, - contextTransactionId, - contextTransaction != null ? contextTransaction.GetHashCode() : 0, - currentSystemTransaction != null ? currentSystemTransaction.GetHashCode() : 0); - - if (SqlInternalTransaction.NullTransactionId != contextTransactionId) - { - if (currentSystemTransaction != null && contextTransaction != currentSystemTransaction) - { - throw SQL.NestedTransactionScopesNotSupported(); // can't use TransactionScope(RequiresNew) inside a Sql Transaction. - } - - SqlClientEventSource.Log.TryTraceEvent(" {0}, using context transaction with transactionId=0x{1}", ObjectID, contextTransactionId); - _currentTransaction = new SqlInternalTransaction(this, TransactionType.Context, null, contextTransactionId); - ContextTransaction = contextTransaction; - } - else if (currentSystemTransaction == null) - { - _currentTransaction = null; // there really isn't a transaction. - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, no transaction.", ObjectID); - } - else - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, using current System.Transaction.", ObjectID); - base.Enlist(currentSystemTransaction); - } - } - - override protected void ChangeDatabaseInternal(string database) - { - _smiConnection.SetCurrentDatabase(database, _smiEventSink); - _smiEventSink.ProcessMessagesAndThrow(); - } - - override protected void InternalDeactivate() - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Deactivating.", ObjectID); - - // When we put this to bed, we should not hold on to the transaction - // or any activity (commit/rollback) may cause it to stop responding. - if (!IsNonPoolableTransactionRoot) - { - base.Enlist(null); - } - - if (_currentTransaction != null) - { - if (_currentTransaction.IsContext) - { - _currentTransaction = null; - } - else if (_currentTransaction.IsLocal) - { - _currentTransaction.CloseFromConnection(); - } - } - - ContextTransaction = null; - - _isInUse = 0; // don't need compare-exchange. - } - - override internal void DelegatedTransactionEnded() - { - base.DelegatedTransactionEnded(); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, cleaning up after Delegated Transaction Completion", ObjectID); - _currentTransaction = null; // clean up our current transaction too - } - - override internal void DisconnectTransaction(SqlInternalTransaction internalTransaction) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Disconnecting Transaction {1}.", ObjectID, internalTransaction.ObjectID); - - // VSTS 215465/15029: allow _currentTransaction to be null - it can be cleared before by server's callback - Debug.Assert(_currentTransaction == null || _currentTransaction == internalTransaction, "disconnecting different transaction"); - - if (_currentTransaction != null && _currentTransaction == internalTransaction) - { - _currentTransaction = null; - } - } - - override public void Dispose() - { - _smiContext.OutOfScope -= new EventHandler(OnOutOfScope); - base.Dispose(); - } - - override internal void ExecuteTransaction( - TransactionRequest transactionRequest, - string transactionName, - System.Data.IsolationLevel iso, - SqlInternalTransaction internalTransaction, - bool isDelegateControlRequest) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent( - " {0}, transactionRequest={1}, " + - "transactionName='{2}', isolationLevel={3}, internalTransaction=#{4} transactionId=0x{5}.", - ObjectID, - transactionRequest, - transactionName, - iso, - internalTransaction != null ? internalTransaction.ObjectID : 0, - internalTransaction != null ? internalTransaction.TransactionId : SqlInternalTransaction.NullTransactionId); - - switch (transactionRequest) - { - case TransactionRequest.Begin: - try - { - _pendingTransaction = internalTransaction; // store this for the time being. - - _smiConnection.BeginTransaction(transactionName, iso, _smiEventSink); - } - finally - { - _pendingTransaction = null; - } - - Debug.Assert(_smiEventSink.HasMessages || _currentTransaction != null, "begin transaction without TransactionStarted event?"); - break; - - case TransactionRequest.Commit: - Debug.Assert(_currentTransaction != null, "commit transaction without TransactionStarted event?"); - - _smiConnection.CommitTransaction(_currentTransaction.TransactionId, _smiEventSink); - break; - - case TransactionRequest.Promote: - Debug.Assert(_currentTransaction != null, "promote transaction without TransactionStarted event?"); - PromotedDTCToken = _smiConnection.PromoteTransaction(_currentTransaction.TransactionId, _smiEventSink); - break; - - case TransactionRequest.Rollback: - case TransactionRequest.IfRollback: - Debug.Assert(_currentTransaction != null, "rollback/ifrollback transaction without TransactionStarted event?"); - _smiConnection.RollbackTransaction(_currentTransaction.TransactionId, transactionName, _smiEventSink); - break; - - case TransactionRequest.Save: - Debug.Assert(_currentTransaction != null, "save transaction without TransactionStarted event?"); - _smiConnection.CreateTransactionSavePoint(_currentTransaction.TransactionId, transactionName, _smiEventSink); - break; - - default: - Debug.Assert(false, "unhandled case for TransactionRequest"); - break; - } - - _smiEventSink.ProcessMessagesAndThrow(); - } - - override protected byte[] GetDTCAddress() - { - byte[] whereAbouts = _smiConnection.GetDTCAddress(_smiEventSink); // might want to store this on the SmiLink because it doesn't change, but we want to be compatible with TDS which doesn't have a link yet. - - _smiEventSink.ProcessMessagesAndThrow(); - - if (whereAbouts != null) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" whereAbouts = {0}, Length = {1}", whereAbouts, (ushort)whereAbouts.Length); - } - else - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" whereAbouts=null"); - } - - return whereAbouts; - } - - internal void GetCurrentTransactionPair(out long transactionId, out Transaction transaction) - { - // SQLBU 214740: Transaction state could change between obtaining tranid and transaction - // due to background SqlDelegatedTransaction processing. Lock the connection to prevent that. - lock (this) - { - transactionId = CurrentTransaction != null ? CurrentTransaction.TransactionId : 0; - transaction = null; - if (0 != transactionId) - { - transaction = InternalEnlistedTransaction; - } - } - } - - private void OnOutOfScope(object s, EventArgs e) - { - // Called whenever the context goes out of scope, we need to make - // sure that we close the connection, or the next person that uses - // the context may appear to have the connection in use. - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} context is out of scope", ObjectID); - - // TODO: This is suspect to me -- if I go out of scope, who will commit the transaction? - DelegatedTransaction = null; // we don't want to hold this over to the next usage; it will automatically be reused as the context transaction... - - DbConnection owningObject = (DbConnection)Owner; - - try - { - if (owningObject != null && 1 == _isInUse) - { - // SQLBU 369953 - // for various reasons, the owning object may no longer be connection to this - // so call close on the owner, rather than trying to bypass to use internal close logic. - owningObject.Close(); - } - } - finally - { - // Now make sure this object is not left in an in-use state - // this is safe, because no user code should be accessing the connection by this time - ContextTransaction = null; - _isInUse = 0; - } - } - - override protected void PropagateTransactionCookie(byte[] transactionCookie) - { - - if (transactionCookie != null) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" transactionCookie", transactionCookie, (UInt16)transactionCookie.Length); - } - else - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" null"); - } - - // Propagate the transaction cookie to the server - _smiConnection.EnlistTransaction(transactionCookie, _smiEventSink); - _smiEventSink.ProcessMessagesAndThrow(); - } - - private void TransactionEndedByServer(long transactionId, TransactionState transactionState) - { - // Some extra steps required when the server initiates the ending of a transaction unilaterally - // as opposed to the client initiating it. - // Basically, we have to make the delegated transaction (if there is one) aware of the situation. - - SqlDelegatedTransaction delegatedTransaction = DelegatedTransaction; - if (delegatedTransaction != null) - { - delegatedTransaction.Transaction.Rollback(); // just to make sure... - DelegatedTransaction = null; // He's dead, Jim. - } - - // Now handle the standard transaction-ended stuff. - TransactionEnded(transactionId, transactionState); - } - - private void TransactionEnded(long transactionId, TransactionState transactionState) - { - // When we get notification of a completed transaction - // we null out the current transaction. - - if (_currentTransaction != null) - { -#if DEBUG - // Check null for case where Begin and Rollback obtained in the same message. - if (0 != _currentTransaction.TransactionId) - { - Debug.Assert(_currentTransaction.TransactionId == transactionId, "transaction id's are not equal!"); - } -#endif - _currentTransaction.Completed(transactionState); - _currentTransaction = null; - } - } - - private void TransactionStarted(long transactionId, bool isDistributed) - { - // When we get notification from the server of a new - // transaction, we move any pending transaction over to - // the current transaction, then we store the token in it. - // if there isn't a pending transaction, then it's either - // a TSQL transaction or a distributed transaction. - Debug.Assert(_currentTransaction == null, "non-null current transaction with an env change"); - _currentTransaction = _pendingTransaction; - _pendingTransaction = null; - - if (_currentTransaction != null) - { - _currentTransaction.TransactionId = transactionId; // this is defined as a ULongLong in the server and in the TDS Spec. - } - else - { - TransactionType transactionType = (isDistributed) ? TransactionType.Distributed : TransactionType.LocalFromTSQL; - _currentTransaction = new SqlInternalTransaction(this, transactionType, null, transactionId); - } - _currentTransaction.Activate(); // SQLBUDT #376531 -- ensure this is activated to prevent asserts later. - } - - override internal void ValidateConnectionForExecute(SqlCommand command) - { - SqlDataReader reader = FindLiveReader(null); - if (reader != null) - { - // if MARS is on, then a datareader associated with the command exists - // or if MARS is off, then a datareader exists - TdsParser parser = command.Connection.Parser; - throw ADP.OpenReaderExists(parser.MARSOn); // MDAC 66411 - } - } - } -} - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index d0c64d07e3..9da128fb6d 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using System.Data; using System.Data.Common; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -15,26 +14,27 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Transactions; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; using Microsoft.Identity.Client; -using System.Transactions; - namespace Microsoft.Data.SqlClient { - internal class SessionStateRecord + internal sealed class SessionStateRecord { internal bool _recoverable; - internal UInt32 _version; - internal Int32 _dataLength; + internal uint _version; + internal int _dataLength; internal byte[] _data; } - internal class SessionData + internal sealed class SessionData { internal const int _maxNumberOfSessionStates = 256; - internal UInt32 _tdsVersion; + internal uint _tdsVersion; internal bool _encrypted; internal string _database; @@ -103,17 +103,17 @@ public void AssertUnrecoverableStateCountIsCorrect() } } - sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposable + internal sealed class SqlInternalConnectionTds : SqlInternalConnection, IDisposable { // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/retry-after#simple-retry-for-errors-with-http-error-codes-500-600 internal const int MsalHttpRetryStatusCode = 429; - // Connection re-route limit - internal const int _maxNumberOfRedirectRoute = 10; + internal const int MaxNumberOfRedirectRoute = 10; // CONNECTION AND STATE VARIABLES private readonly SqlConnectionPoolGroupProviderInfo _poolGroupProviderInfo; // will only be null when called for ChangePassword, or creating SSE User Instance private TdsParser _parser; + private SqlLoginAck _loginAck; private SqlCredential _credential; private FederatedAuthenticationFeatureExtensionData _fedAuthFeatureExtensionData; @@ -127,7 +127,6 @@ sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposa // Federated Authentication // Response obtained from the server for FEDAUTHREQUIRED prelogin option. internal bool _fedAuthRequired; - internal bool _federatedAuthenticationRequested; internal bool _federatedAuthenticationAcknowledged; internal bool _federatedAuthenticationInfoRequested; // Keep this distinct from _federatedAuthenticationRequested, since some fedauth library types may not need more info @@ -136,7 +135,7 @@ sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposa // The Federated Authentication returned by TryGetFedAuthTokenLocked or GetFedAuthToken. SqlFedAuthToken _fedAuthToken = null; internal byte[] _accessTokenInBytes; - internal readonly Func> _accessTokenCallback; + internal readonly Func> _accessTokenCallback; private readonly ActiveDirectoryAuthenticationTimeoutRetryHelper _activeDirectoryAuthTimeoutRetryHelper; @@ -156,7 +155,7 @@ private int accessTokenExpirationBufferTime } /// - /// Get or set if SQLDNSCaching FeatureExtAck is supported by the server. + /// Get or set if SQLDNSCaching is supported by the server. /// internal bool IsSQLDNSCachingSupported { @@ -187,20 +186,20 @@ internal bool IsSQLDNSRetryEnabled } } - private bool DNSCachingBeforeRedirect = false; + private bool _dnsCachingBeforeRedirect = false; /// - /// Get or set if the control ring send redirect token and SQLDNSCaching FeatureExtAck with true + /// Get or set if the control ring send redirect token and feature ext ack with true for DNSCaching /// internal bool IsDNSCachingBeforeRedirectSupported { get { - return DNSCachingBeforeRedirect; + return _dnsCachingBeforeRedirect; } set { - DNSCachingBeforeRedirect = value; + _dnsCachingBeforeRedirect = value; } } @@ -209,11 +208,14 @@ internal bool IsDNSCachingBeforeRedirectSupported // Json Support Flag internal bool IsJsonSupportEnabled = false; + // Vector Support Flag + internal bool IsVectorSupportEnabled = false; + // TCE flags internal byte _tceVersionSupported; // The pool that this connection is associated with, if at all it is. - private DbConnectionPool _dbConnectionPool; + private IDbConnectionPool _dbConnectionPool; // This is used to preserve the authentication context object if we decide to cache it for subsequent connections in the same pool. // This will finally end up in _dbConnectionPool.AuthenticationContexts, but only after 1 successful login to SQL Server using this context. @@ -252,9 +254,54 @@ internal bool IsDNSCachingBeforeRedirectSupported // If the context is expiring within the next 10 mins, then create a new context, irrespective of if another thread is trying to do the same. private static readonly TimeSpan _dbAuthenticationContextUnLockedRefreshTimeSpan = new TimeSpan(hours: 0, minutes: 10, seconds: 00); - private readonly TimeoutTimer _timeout; + // The errors in the transient error set are contained in + // https://azure.microsoft.com/en-us/documentation/articles/sql-database-develop-error-messages/#transient-faults-connection-loss-and-other-temporary-errors + private static readonly HashSet s_transientErrors = new HashSet + { + // SQL Error Code: 4060 + // Cannot open database "%.*ls" requested by the login. The login failed. + 4060, + + // SQL Error Code: 10928 + // Resource ID: %d. The %s limit for the database is %d and has been reached. + 10928, + + // SQL Error Code: 10929 + // Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d. + // However, the server is currently too busy to support requests greater than %d for this database. + 10929, - private static HashSet transientErrors = new HashSet(); + // SQL Error Code: 40197 + // You will receive this error, when the service is down due to software or hardware upgrades, hardware failures, + // or any other failover problems. The error code (%d) embedded within the message of error 40197 provides + // additional information about the kind of failure or failover that occurred. Some examples of the error codes are + // embedded within the message of error 40197 are 40020, 40143, 40166, and 40540. + 40197, + 40020, + 40143, + 40166, + + // The service has encountered an error processing your request. Please try again. + 40540, + + // The service is currently busy. Retry the request after 10 seconds. Incident ID: %ls. Code: %d. + 40501, + + // Database '%.*ls' on server '%.*ls' is not currently available. Please retry the connection later. + // If the problem persists, contact customer support, and provide them the session tracing ID of '%.*ls'. + 40613, + + // Can not connect to the SQL pool since it is paused. Please resume the SQL pool and try again. + 42108, + + // The SQL pool is warming up. Please try again. + 42109 + + // Do federation errors deserve to be here ? + // Note: Federation errors 10053 and 10054 might also deserve inclusion in your retry logic. + // 10053 + // 10054 + }; internal SessionData CurrentSessionData { @@ -293,28 +340,28 @@ internal SessionData CurrentSessionData // 2. _parserLock will also be taken during close (to prevent closing in the middle of a write) // 3. Whenever you have the _parserLock and are calling a method that would cause the connection to close if it failed (with the exception of any writing method), you MUST set ThreadHasParserLockForClose to true // * This is to prevent the connection deadlocking with itself (since you already have the _parserLock, and Closing the connection will attempt to re-take that lock) - // * It is safe to set ThreadHasParserLockForClose to true when writing as well, but it is unneccesary + // * It is safe to set ThreadHasParserLockForClose to true when writing as well, but it is unnecessary // * If you have a method that takes _parserLock, it is a good idea check ThreadHasParserLockForClose first (if you don't expect _parserLock to be taken by something higher on the stack, then you should at least assert that it is false) // 4. ThreadHasParserLockForClose is thread-specific - this means that you must set it to false before returning a Task, and set it back to true in the continuation // 5. ThreadHasParserLockForClose should only be modified if you currently own the _parserLock // 6. Reading ThreadHasParserLockForClose is thread-safe internal class SyncAsyncLock { - SemaphoreSlim semaphore = new SemaphoreSlim(1); + private SemaphoreSlim _semaphore = new SemaphoreSlim(1); internal void Wait(bool canReleaseFromAnyThread) { - Monitor.Enter(semaphore); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods - if (canReleaseFromAnyThread || semaphore.CurrentCount == 0) + Monitor.Enter(_semaphore); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods + if (canReleaseFromAnyThread || _semaphore.CurrentCount == 0) { - semaphore.Wait(); + _semaphore.Wait(); if (canReleaseFromAnyThread) { - Monitor.Exit(semaphore); + Monitor.Exit(_semaphore); } else { - semaphore.Release(); + _semaphore.Release(); } } } @@ -325,21 +372,21 @@ internal void Wait(bool canReleaseFromAnyThread, int timeout, ref bool lockTaken bool hasMonitor = false; try { - Monitor.TryEnter(semaphore, timeout, ref hasMonitor); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods + Monitor.TryEnter(_semaphore, timeout, ref hasMonitor); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods if (hasMonitor) { - if ((canReleaseFromAnyThread) || (semaphore.CurrentCount == 0)) + if ((canReleaseFromAnyThread) || (_semaphore.CurrentCount == 0)) { - if (semaphore.Wait(timeout)) + if (_semaphore.Wait(timeout)) { if (canReleaseFromAnyThread) { - Monitor.Exit(semaphore); + Monitor.Exit(_semaphore); hasMonitor = false; } else { - semaphore.Release(); + _semaphore.Release(); } lockTaken = true; } @@ -354,20 +401,20 @@ internal void Wait(bool canReleaseFromAnyThread, int timeout, ref bool lockTaken { if ((!lockTaken) && (hasMonitor)) { - Monitor.Exit(semaphore); + Monitor.Exit(_semaphore); } } } internal void Release() { - if (semaphore.CurrentCount == 0) + if (_semaphore.CurrentCount == 0) { // semaphore methods were used for locking - semaphore.Release(); + _semaphore.Release(); } else { - Monitor.Exit(semaphore); + Monitor.Exit(_semaphore); } } @@ -376,14 +423,14 @@ internal bool CanBeReleasedFromAnyThread { get { - return semaphore.CurrentCount == 0; + return _semaphore.CurrentCount == 0; } } - // Necessary but not sufficient condition for thread to have lock (since sempahore may be obtained by any thread) + // Necessary but not sufficient condition for thread to have lock (since semaphore may be obtained by any thread) internal bool ThreadMayHaveLock() { - return Monitor.IsEntered(semaphore) || semaphore.CurrentCount == 0; + return Monitor.IsEntered(_semaphore) || _semaphore.CurrentCount == 0; } } @@ -399,46 +446,41 @@ internal SqlConnectionTimeoutErrorInternal TimeoutErrorInternal } // OTHER STATE VARIABLES AND REFERENCES - internal Guid _clientConnectionId = Guid.Empty; // Routing information (ROR) - RoutingInfo _routingInfo = null; private Guid _originalClientConnectionId = Guid.Empty; private string _routingDestination = null; + private readonly TimeoutTimer _timeout; - static SqlInternalConnectionTds() - { - populateTransientErrors(); - } - - // although the new password is generally not used it must be passed to the c'tor + // although the new password is generally not used it must be passed to the ctor // the new Login7 packet will always write out the new password (or a length of zero and no bytes if not present) // internal SqlInternalConnectionTds( DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, - object providerInfo, + DbConnectionPoolGroupProviderInfo providerInfo, string newPassword, SecureString newSecurePassword, bool redirectedUserInstance, SqlConnectionString userConnectionOptions = null, // NOTE: userConnectionOptions may be different to connectionOptions if the connection string has been expanded (see SqlConnectionString.Expand) SessionData reconnectSessionData = null, - DbConnectionPool pool = null, - string accessToken = null, bool applyTransientFaultHandling = false, + string accessToken = null, + IDbConnectionPool pool = null, Func> accessTokenCallback = null) : base(connectionOptions) + Task> accessTokenCallback = null) + : base(connectionOptions) { - #if DEBUG if (reconnectSessionData != null) { reconnectSessionData._debugReconnectDataApplied = true; } try - { // use this to help validate this object is only created after the following permission has been previously demanded in the current codepath + { + // use this to help validate this object is only created after the following permission has been previously demanded in the current codepath if (userConnectionOptions != null) { // As mentioned above, userConnectionOptions may be different to connectionOptions, so we need to demand on the correct connection string @@ -474,11 +516,6 @@ internal SqlInternalConnectionTds( } } - if (connectionOptions.UserInstance && InOutOfProcHelper.InProc) - { - throw SQL.UserInstanceNotAvailableInProc(); - } - if (accessToken != null) { _accessTokenInBytes = System.Text.Encoding.Unicode.GetBytes(accessToken); @@ -491,9 +528,7 @@ internal SqlInternalConnectionTds( _identity = identity; Debug.Assert(newSecurePassword != null || newPassword != null, "cannot have both new secure change password and string based change password to be null"); Debug.Assert(credential == null || (string.IsNullOrEmpty(connectionOptions.UserID) && string.IsNullOrEmpty(connectionOptions.Password)), "cannot mix the new secure password system and the connection string based password"); - Debug.Assert(credential == null || !connectionOptions.IntegratedSecurity, "Cannot use SqlCredential and Integrated Security"); - Debug.Assert(credential == null || !connectionOptions.ContextConnection, "Cannot use SqlCredential with context connection"); _poolGroupProviderInfo = (SqlConnectionPoolGroupProviderInfo)providerInfo; _fResetConnection = connectionOptions.ConnectionReset; @@ -508,54 +543,38 @@ internal SqlInternalConnectionTds( _parserLock.Wait(canReleaseFromAnyThread: false); ThreadHasParserLockForClose = true; // In case of error, let ourselves know that we already own the parser lock + RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); + _timeout = TimeoutTimer.StartSecondsTimeout(connectionOptions.ConnectTimeout); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else + // If transient fault handling is enabled then we can retry the login up to the ConnectRetryCount. + int connectionEstablishCount = applyTransientFaultHandling ? connectionOptions.ConnectRetryCount + 1 : 1; + int transientRetryIntervalInMilliSeconds = connectionOptions.ConnectRetryInterval * 1000; // Max value of transientRetryInterval is 60*1000 ms. The max value allowed for ConnectRetryInterval is 60 + for (int i = 0; i < connectionEstablishCount; i++) { -#endif //DEBUG - _timeout = TimeoutTimer.StartSecondsTimeout(connectionOptions.ConnectTimeout); - - // If transient fault handling is enabled then we can retry the login upto the ConnectRetryCount. - int connectionEstablishCount = applyTransientFaultHandling ? connectionOptions.ConnectRetryCount + 1 : 1; - int transientRetryIntervalInMilliSeconds = connectionOptions.ConnectRetryInterval * 1000; // Max value of transientRetryInterval is 60*1000 ms. The max value allowed for ConnectRetryInterval is 60 - for (int i = 0; i < connectionEstablishCount; i++) + try { - try + OpenLoginEnlist(_timeout, connectionOptions, credential, newPassword, newSecurePassword, redirectedUserInstance); + break; + } + catch (SqlException sqlex) + { + if (i + 1 == connectionEstablishCount + || !applyTransientFaultHandling + || _timeout.IsExpired + || _timeout.MillisecondsRemaining < transientRetryIntervalInMilliSeconds + || !IsTransientError(sqlex)) { - OpenLoginEnlist(_timeout, connectionOptions, credential, newPassword, newSecurePassword, redirectedUserInstance); - break; + throw; } - catch (SqlException sqlex) + else { - if (i + 1 == connectionEstablishCount - || !applyTransientFaultHandling - || _timeout.IsExpired - || _timeout.MillisecondsRemaining < transientRetryIntervalInMilliSeconds - || !IsTransientError(sqlex)) - { - throw; - } - else - { - Thread.Sleep(transientRetryIntervalInMilliSeconds); - } + Thread.Sleep(transientRetryIntervalInMilliSeconds); } } } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG } catch (System.OutOfMemoryException) { @@ -580,49 +599,6 @@ internal SqlInternalConnectionTds( SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, constructed new TDS internal connection", ObjectID); } - // The errors in the transient error set are contained in - // https://azure.microsoft.com/en-us/documentation/articles/sql-database-develop-error-messages/#transient-faults-connection-loss-and-other-temporary-errors - private static void populateTransientErrors() - { - // SQL Error Code: 4060 - // Cannot open database "%.*ls" requested by the login. The login failed. - transientErrors.Add(4060); - // SQL Error Code: 10928 - // Resource ID: %d. The %s limit for the database is %d and has been reached. - transientErrors.Add(10928); - // SQL Error Code: 10929 - // Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d. - // However, the server is currently too busy to support requests greater than %d for this database. - transientErrors.Add(10929); - // SQL Error Code: 40197 - // You will receive this error, when the service is down due to software or hardware upgrades, hardware failures, - // or any other failover problems. The error code (%d) embedded within the message of error 40197 provides - // additional information about the kind of failure or failover that occurred. Some examples of the error codes are - // embedded within the message of error 40197 are 40020, 40143, 40166, and 40540. - transientErrors.Add(40197); - transientErrors.Add(40020); - transientErrors.Add(40143); - transientErrors.Add(40166); - // The service has encountered an error processing your request. Please try again. - transientErrors.Add(40540); - // The service is currently busy. Retry the request after 10 seconds. Incident ID: %ls. Code: %d. - transientErrors.Add(40501); - // Database '%.*ls' on server '%.*ls' is not currently available. Please retry the connection later. - // If the problem persists, contact customer support, and provide them the session tracing ID of '%.*ls'. - transientErrors.Add(40613); - - // Can not connect to the SQL pool since it is paused. Please resume the SQL pool and try again. - transientErrors.Add(42108); - - // The SQL pool is warming up. Please try again. - transientErrors.Add(42109); - // Do federation errors deserve to be here ? - // Note: Federation errors 10053 and 10054 might also deserve inclusion in your retry logic. - //transientErrors.Add(10053); - //transientErrors.Add(10054); - } - - // Returns true if the Sql error is a transient. private bool IsTransientError(SqlException exc) { @@ -632,7 +608,7 @@ private bool IsTransientError(SqlException exc) } foreach (SqlError error in exc.Errors) { - if (transientErrors.Contains(error.Number)) + if (s_transientErrors.Contains(error.Number)) { // When server timeouts, connection is doomed. Reset here to allow reconnect. UnDoomThisConnection(); @@ -666,15 +642,9 @@ internal string RoutingDestination } } - internal RoutingInfo RoutingInfo - { - get - { - return _routingInfo; - } - } + internal RoutingInfo RoutingInfo { get; private set; } = null; - override internal SqlInternalTransaction CurrentTransaction + internal override SqlInternalTransaction CurrentTransaction { get { @@ -682,7 +652,7 @@ override internal SqlInternalTransaction CurrentTransaction } } - override internal SqlInternalTransaction AvailableInternalTransaction + internal override SqlInternalTransaction AvailableInternalTransaction { get { @@ -690,8 +660,7 @@ override internal SqlInternalTransaction AvailableInternalTransaction } } - - override internal SqlInternalTransaction PendingTransaction + internal override SqlInternalTransaction PendingTransaction { get { @@ -715,7 +684,7 @@ internal string InstanceName } } - override internal bool IsLockedForBulkCopy + internal override bool IsLockedForBulkCopy { get { @@ -723,7 +692,7 @@ override internal bool IsLockedForBulkCopy } } - override protected internal bool IsNonPoolableTransactionRoot + protected internal override bool IsNonPoolableTransactionRoot { get { @@ -731,23 +700,7 @@ override protected internal bool IsNonPoolableTransactionRoot } } - override internal bool Is2000 - { - get - { - return _loginAck.isVersion8; - } - } - - override internal bool Is2005OrNewer - { - get - { - return _parser.Is2005OrNewer; - } - } - - override internal bool Is2008OrNewer + internal override bool Is2008OrNewer { get { @@ -787,7 +740,7 @@ internal SqlConnectionPoolGroupProviderInfo PoolGroupProviderInfo } } - override protected bool ReadyToPrepareTransaction + protected override bool ReadyToPrepareTransaction { get { @@ -797,7 +750,7 @@ override protected bool ReadyToPrepareTransaction } } - override public string ServerVersion + public override string ServerVersion { get { @@ -805,6 +758,7 @@ override public string ServerVersion (short)_loginAck.minorVersion, _loginAck.buildNum)); } } + public int ServerProcessId { get @@ -840,16 +794,16 @@ protected override bool UnbindOnTransactionCompletion // GENERAL METHODS //////////////////////////////////////////////////////////////////////////////////////// [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] // copied from Triaged.cs - override protected void ChangeDatabaseInternal(string database) + protected override void ChangeDatabaseInternal(string database) { // MDAC 73598 - add brackets around database database = SqlConnection.FixupDatabaseTransactionName(database); - System.Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch("use " + database, ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true); + Task executeTask = _parser.TdsExecuteSQLBatch("use " + database, ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true); Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes"); _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj); } - override public void Dispose() + public override void Dispose() { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} disposing", ObjectID); try @@ -863,7 +817,7 @@ override public void Dispose() } } finally - { // UNDONE: MDAC 77928 + { // close will always close, even if exception is thrown // remember to null out any object references _loginAck = null; @@ -872,7 +826,7 @@ override public void Dispose() base.Dispose(); } - override internal void ValidateConnectionForExecute(SqlCommand command) + internal override void ValidateConnectionForExecute(SqlCommand command) { TdsParser parser = _parser; if ((parser == null) || (parser.State == TdsParserState.Broken) || (parser.State == TdsParserState.Closed)) @@ -933,7 +887,7 @@ override internal void ValidateConnectionForExecute(SqlCommand command) /// /// When using Implicit transaction unbinding, /// verify that the enlisted transaction is active. - /// If it is not active, and the transaction object has been diposed, unbind from the transaction. + /// If it is not active, and the transaction object has been disposed, unbind from the transaction. /// If it is not active and not disposed, throw an exception. /// /// @@ -973,35 +927,14 @@ internal void CheckEnlistedTransactionBinding() } } - internal override bool IsConnectionAlive(bool throwOnException) - { - bool isAlive = false; -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#endif //DEBUG - - isAlive = _parser._physicalStateObj.IsConnectionAlive(throwOnException); - -#if DEBUG - } - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - return isAlive; - } + internal override bool IsConnectionAlive(bool throwOnException) => + _parser._physicalStateObj.IsConnectionAlive(throwOnException); //////////////////////////////////////////////////////////////////////////////////////// // POOLING METHODS //////////////////////////////////////////////////////////////////////////////////////// - override protected void Activate(Transaction transaction) + protected override void Activate(Transaction transaction) { FailoverPermissionDemand(); // Demand for unspecified failover pooled connections @@ -1026,7 +959,7 @@ override protected void Activate(Transaction transaction) } } - override protected void InternalDeactivate() + protected override void InternalDeactivate() { // When we're deactivated, the user must have called End on all // the async commands, or we don't know that we're in a state that @@ -1047,7 +980,6 @@ override protected void InternalDeactivate() Debug.Assert(_parser != null || IsConnectionDoomed, "Deactivating a disposed connection?"); if (_parser != null) { - _parser.Deactivate(IsConnectionDoomed); if (!IsConnectionDoomed) @@ -1063,7 +995,7 @@ private void ResetConnection() { // For implicit pooled connections, if connection reset behavior is specified, // reset the database and language properties back to default. It is important - // to do this on activate so that the hashtable is correct before SqlConnection + // to do this on activate so that the dictionary is correct before SqlConnection // obtains a clone. Debug.Assert(!HasLocalTransactionFromAPI, "Upon ResetConnection SqlInternalConnectionTds has a currently ongoing local transaction."); @@ -1071,39 +1003,13 @@ private void ResetConnection() if (_fResetConnection) { - // Ensure we are either going against 2000, or we are not enlisted in a - // distributed transaction - otherwise don't reset! - if (Is2000) - { - // Prepare the parser for the connection reset - the next time a trip - // to the server is made. - _parser.PrepareResetConnection(IsTransactionRoot && !IsNonPoolableTransactionRoot); - } - else if (!IsEnlistedInTransaction) - { - // If not 2000, we are going against 7.0. On 7.0, we - // may only reset if not enlisted in a distributed transaction. - try - { - // execute sp - System.Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch("sp_reset_connection", 30, null, _parser._physicalStateObj, sync: true); - Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes"); - _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj); - } - catch (Exception e) - { - // UNDONE - should not be catching all exceptions!!! - if (!ADP.IsCatchableExceptionType(e)) - { - throw; - } + // Pooled connections that are enlisted in a transaction must have their transaction + // preserved when reseting the connection state. Otherwise, future uses of the connection + // from the pool will execute outside of the transaction, in auto-commit mode. + // https://github.com/dotnet/SqlClient/issues/2970 + _parser.PrepareResetConnection(EnlistedTransaction is not null && Pool is not null); - DoomThisConnection(); - ADP.TraceExceptionWithoutRethrow(e); - } - } - - // Reset hashtable values, since calling reset will not send us env_changes. + // Reset dictionary values, since calling reset will not send us env_changes. CurrentDatabase = _originalDatabase; _currentLanguage = _originalLanguage; } @@ -1125,7 +1031,7 @@ internal void IncrementAsyncCount() // LOCAL TRANSACTION METHODS //////////////////////////////////////////////////////////////////////////////////////// - override internal void DisconnectTransaction(SqlInternalTransaction internalTransaction) + internal override void DisconnectTransaction(SqlInternalTransaction internalTransaction) { TdsParser parser = Parser; @@ -1140,7 +1046,7 @@ internal void ExecuteTransaction(TransactionRequest transactionRequest, string n ExecuteTransaction(transactionRequest, name, iso, null, false); } - override internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, System.Data.IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) + internal override void ExecuteTransaction(TransactionRequest transactionRequest, string name, System.Data.IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) { if (IsConnectionDoomed) { // doomed means we can't do anything else... @@ -1164,103 +1070,7 @@ override internal void ExecuteTransaction(TransactionRequest transactionRequest, string transactionName = name == null ? string.Empty : name; - if (!_parser.Is2005OrNewer) - { - ExecuteTransactionPre2005(transactionRequest, transactionName, iso, internalTransaction); - } - else - { - ExecuteTransaction2005(transactionRequest, transactionName, iso, internalTransaction, isDelegateControlRequest); - } - } - - // This function will not handle idle connection resiliency, as older servers will not support it - internal void ExecuteTransactionPre2005( - TransactionRequest transactionRequest, - string transactionName, - System.Data.IsolationLevel iso, - SqlInternalTransaction internalTransaction) - { - StringBuilder sqlBatch = new StringBuilder(); - - switch (iso) - { - case System.Data.IsolationLevel.Unspecified: - break; - case System.Data.IsolationLevel.ReadCommitted: - sqlBatch.Append(TdsEnums.TRANS_READ_COMMITTED); - sqlBatch.Append(";"); - break; - case System.Data.IsolationLevel.ReadUncommitted: - sqlBatch.Append(TdsEnums.TRANS_READ_UNCOMMITTED); - sqlBatch.Append(";"); - break; - case System.Data.IsolationLevel.RepeatableRead: - sqlBatch.Append(TdsEnums.TRANS_REPEATABLE_READ); - sqlBatch.Append(";"); - break; - case System.Data.IsolationLevel.Serializable: - sqlBatch.Append(TdsEnums.TRANS_SERIALIZABLE); - sqlBatch.Append(";"); - break; - case System.Data.IsolationLevel.Snapshot: - throw SQL.SnapshotNotSupported(System.Data.IsolationLevel.Snapshot); - - case System.Data.IsolationLevel.Chaos: - throw SQL.NotSupportedIsolationLevel(iso); - - default: - throw ADP.InvalidIsolationLevel(iso); - } - - if (!ADP.IsEmpty(transactionName)) - { - transactionName = " " + SqlConnection.FixupDatabaseTransactionName(transactionName); - } - - switch (transactionRequest) - { - case TransactionRequest.Begin: - sqlBatch.Append(TdsEnums.TRANS_BEGIN); - sqlBatch.Append(transactionName); - break; - case TransactionRequest.Promote: - Debug.Assert(false, "Promote called with transaction name or on pre-2005!"); - break; - case TransactionRequest.Commit: - sqlBatch.Append(TdsEnums.TRANS_COMMIT); - sqlBatch.Append(transactionName); - break; - case TransactionRequest.Rollback: - sqlBatch.Append(TdsEnums.TRANS_ROLLBACK); - sqlBatch.Append(transactionName); - break; - case TransactionRequest.IfRollback: - sqlBatch.Append(TdsEnums.TRANS_IF_ROLLBACK); - sqlBatch.Append(transactionName); - break; - case TransactionRequest.Save: - sqlBatch.Append(TdsEnums.TRANS_SAVE); - sqlBatch.Append(transactionName); - break; - default: - Debug.Fail("Unknown transaction type"); - break; - } - - System.Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch(sqlBatch.ToString(), ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true); - Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes"); - _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj); - - // Prior to 2005, we didn't have any transaction tokens to manage, - // or any feedback to know when one was created, so we just presume - // that successful execution of the request caused the transaction - // to be created, and we set that on the parser. - if (TransactionRequest.Begin == transactionRequest) - { - Debug.Assert(internalTransaction != null, "Begin Transaction request without internal transaction"); - _parser.CurrentTransaction = internalTransaction; - } + ExecuteTransaction2005(transactionRequest, transactionName, iso, internalTransaction, isDelegateControlRequest); } @@ -1357,19 +1167,17 @@ internal void ExecuteTransaction2005( } } - - // SQLBUDT #20010853 - Promote, Commit and Rollback requests for // delegated transactions often happen while there is an open result // set, so we need to handle them by using a different MARS session, // otherwise we'll write on the physical state objects while someone // else is using it. When we don't have MARS enabled, we need to - // lock the physical state object to syncronize it's use at least + // lock the physical state object to synchronize it's use at least // until we increment the open results count. Once it's been // incremented the delegated transaction requests will fail, so they // won't stomp on anything. // - // We need to keep this lock through the duration of the TM reqeuest + // We need to keep this lock through the duration of the TM request // so that we won't hijack a different request's data stream and a // different request won't hijack ours, so we have a lock here on // an object that the ExecTMReq will also lock, but since we're on @@ -1392,7 +1200,7 @@ internal void ExecuteTransaction2005( } } - // SQLBU #406778 - _parser may be nulled out during TdsExecuteTrannsactionManagerRequest. + // SQLBU #406778 - _parser may be nulled out during TdsExecuteTransactionManagerRequest. // Only use local variable after this call. _parser.TdsExecuteTransactionManagerRequest(null, requestType, transactionName, isoLevel, ConnectionOptions.ConnectTimeout, internalTransaction, stateObj, isDelegateControlRequest); @@ -1416,20 +1224,20 @@ internal void ExecuteTransaction2005( // DISTRIBUTED TRANSACTION METHODS //////////////////////////////////////////////////////////////////////////////////////// - override internal void DelegatedTransactionEnded() + internal override void DelegatedTransactionEnded() { // TODO: I don't like the way that this works, but I don't want to rototill the entire pooler to avoid this call. base.DelegatedTransactionEnded(); } - override protected byte[] GetDTCAddress() + protected override byte[] GetDTCAddress() { byte[] dtcAddress = _parser.GetDTCAddress(ConnectionOptions.ConnectTimeout, _parser.GetSession(this)); Debug.Assert(dtcAddress != null, "null dtcAddress?"); return dtcAddress; } - override protected void PropagateTransactionCookie(byte[] cookie) + protected override void PropagateTransactionCookie(byte[] cookie) { _parser.PropagateDistributedTransaction(cookie, ConnectionOptions.ConnectTimeout, _parser._physicalStateObj); } @@ -1442,7 +1250,7 @@ private void CompleteLogin(bool enlistOK) { _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj); - if (_routingInfo == null) + if (RoutingInfo == null) { // ROR should not affect state of connection recovery if (_federatedAuthenticationRequested && !_federatedAuthenticationAcknowledged) @@ -1494,12 +1302,13 @@ private void CompleteLogin(bool enlistOK) // for non-pooled connections, enlist in a distributed transaction // if present - and user specified to enlist - if (enlistOK && ConnectionOptions.Enlist && _routingInfo == null) + if (enlistOK && ConnectionOptions.Enlist && RoutingInfo == null) { _parser._physicalStateObj.SniContext = SniContext.Snix_AutoEnlist; Transaction tx = ADP.GetCurrentTransaction(); Enlist(tx); } + _parser._physicalStateObj.SniContext = SniContext.Snix_Login; } @@ -1544,9 +1353,9 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, login.language = _currentLanguage; if (!login.userInstance) - { // Do not send attachdbfilename or database to SSE primary instance + { + // Do not send attachdbfilename or database to SSE primary instance login.database = CurrentDatabase; - ; login.attachDBFilename = ConnectionOptions.AttachDBFilename; } @@ -1579,8 +1388,8 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, // in Login7, indicating the intent to use Active Directory Authentication for SQL Server. if (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryPassword || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive - || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow + || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault @@ -1613,10 +1422,8 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, _federatedAuthenticationRequested = true; } - // The TCE, DATACLASSIFICATION and GLOBALTRANSACTIONS, UTF8 support feature are implicitly requested - requestedFeatures |= TdsEnums.FeatureExtension.Tce | TdsEnums.FeatureExtension.DataClassification | TdsEnums.FeatureExtension.GlobalTransactions; - - requestedFeatures |= TdsEnums.FeatureExtension.UTF8Support; + // The GLOBALTRANSACTIONS, DATACLASSIFICATION, TCE, and UTF8 support features are implicitly requested + requestedFeatures |= TdsEnums.FeatureExtension.GlobalTransactions | TdsEnums.FeatureExtension.DataClassification | TdsEnums.FeatureExtension.Tce | TdsEnums.FeatureExtension.UTF8Support; // The AzureSQLSupport feature is implicitly set for ReadOnly login if (ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly) @@ -1624,10 +1431,10 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, requestedFeatures |= TdsEnums.FeatureExtension.AzureSQLSupport; } - // The SQLDNSCaching feature is implicitly set + // The SQLDNSCaching and JSON features are implicitly set requestedFeatures |= TdsEnums.FeatureExtension.SQLDNSCaching; - requestedFeatures |= TdsEnums.FeatureExtension.JsonSupport; + requestedFeatures |= TdsEnums.FeatureExtension.VectorSupport; _parser.TdsLogin(login, requestedFeatures, _recoverySessionData, _fedAuthFeatureExtensionData, encrypt); } @@ -1642,14 +1449,16 @@ private void LoginFailure() // no-op, so no issues there. if (_parser != null) { - _parser.Disconnect(); } - // TODO: Need a performance counter for Failed Connections } - private void OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectionOptions, SqlCredential credential, - string newPassword, SecureString newSecurePassword, bool redirectedUserInstance) + private void OpenLoginEnlist(TimeoutTimer timeout, + SqlConnectionString connectionOptions, + SqlCredential credential, + string newPassword, + SecureString newSecurePassword, + bool redirectedUserInstance) { bool useFailoverPartner; // should we use primary or secondary first ServerInfo dataSource = new ServerInfo(connectionOptions); @@ -1669,7 +1478,7 @@ private void OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectio _timeoutErrorInternal.SetInternalSourceType(useFailoverPartner ? SqlConnectionInternalSourceType.Failover : SqlConnectionInternalSourceType.Principle); - bool hasFailoverPartner = !ADP.IsEmpty(failoverPartner); + bool hasFailoverPartner = !string.IsNullOrEmpty(failoverPartner); // Open the connection and Login try @@ -1692,7 +1501,8 @@ private void OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectio else { _timeoutErrorInternal.SetFailoverScenario(false); // not a failover scenario - LoginNoFailover(dataSource, + LoginNoFailover( + dataSource, newPassword, newSecurePassword, redirectedUserInstance, @@ -1721,7 +1531,6 @@ private void OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectio } catch (Exception e) { - // UNDONE - should not be catching all exceptions!!! if (ADP.IsCatchableExceptionType(e)) { LoginFailure(); @@ -1739,11 +1548,10 @@ private void OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectio // to connect. private bool IsDoNotRetryConnectError(SqlException exc) { - return (TdsEnums.LOGON_FAILED == exc.Number) // actual logon failed, i.e. bad password || (TdsEnums.PASSWORD_EXPIRED == exc.Number) // actual logon failed, i.e. password isExpired - || (TdsEnums.IMPERSONATION_FAILED == exc.Number) // Insuficient privelege for named pipe, among others - || exc._doNotReconnect; // Exception explicitly supressed reconnection attempts + || (TdsEnums.IMPERSONATION_FAILED == exc.Number) // Insufficient privilege for named pipe, among others + || exc._doNotReconnect; // Exception explicitly suppressed reconnection attempts } // Attempt to login to a host that does not have a failover partner @@ -1755,10 +1563,14 @@ private bool IsDoNotRetryConnectError(SqlException exc) // DEVNOTE: The logic in this method is paralleled by the logic in LoginWithFailover. // Changes to either one should be examined to see if they need to be reflected in the other // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureString newSecurePassword, bool redirectedUserInstance, - SqlConnectionString connectionOptions, SqlCredential credential, TimeoutTimer timeout) + private void LoginNoFailover(ServerInfo serverInfo, + string newPassword, + SecureString newSecurePassword, + bool redirectedUserInstance, + SqlConnectionString connectionOptions, + SqlCredential credential, + TimeoutTimer timeout) { - Debug.Assert(object.ReferenceEquals(connectionOptions, this.ConnectionOptions), "ConnectionOptions argument and property must be the same"); // consider removing the argument int routingAttempts = 0; ServerInfo originalServerInfo = serverInfo; // serverInfo may end up pointing to new object due to routing, original object is used to set CurrentDatasource @@ -1769,16 +1581,16 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt ResolveExtendedServerName(serverInfo, !redirectedUserInstance, connectionOptions); - Boolean disableTnir = ShouldDisableTnir(connectionOptions); + bool disableTnir = ShouldDisableTnir(connectionOptions); long timeoutUnitInterval = 0; - Boolean isParallel = connectionOptions.MultiSubnetFailover || (connectionOptions.TransparentNetworkIPResolution && !disableTnir); - + bool isParallel = connectionOptions.MultiSubnetFailover || (connectionOptions.TransparentNetworkIPResolution && !disableTnir); if (isParallel) { float failoverTimeoutStep = connectionOptions.MultiSubnetFailover ? ADP.FailoverTimeoutStep : ADP.FailoverTimeoutStepForTnir; + // Determine unit interval if (timeout.IsInfinite) { @@ -1800,11 +1612,10 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt // back into the parser for the error cases. int attemptNumber = 0; TimeoutTimer intervalTimer = null; - TimeoutTimer attemptOneLoginTimeout = timeout; + while (true) { - Boolean isFirstTransparentAttempt = connectionOptions.TransparentNetworkIPResolution && !disableTnir && attemptNumber == 1; if (isParallel) @@ -1840,11 +1651,11 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt _parser.Disconnect(); _parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous); - Debug.Assert(SniContext.Undefined == Parser._physicalStateObj.SniContext, string.Format((IFormatProvider)null, "SniContext should be Undefined; actual Value: {0}", Parser._physicalStateObj.SniContext)); + Debug.Assert(SniContext.Undefined == Parser._physicalStateObj.SniContext, $"SniContext should be Undefined; actual Value: {Parser._physicalStateObj.SniContext}"); try { - // UNDONE: ITEM12001110 (DB Mirroring Reconnect) Old behavior of not truly honoring timeout presevered + // UNDONE: ITEM12001110 (DB Mirroring Reconnect) Old behavior of not truly honoring timeout preserved // for non-failover, non-MSF scenarios to avoid breaking changes as part of a QFE. Consider fixing timeout // handling in next full release and removing ignoreSniOpenTimeout parameter. @@ -1862,17 +1673,16 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt if (connectionOptions.MultiSubnetFailover && ServerProvidedFailOverPartner != null) { - // connection succeeded: trigger exception if server sends failover partner and MultiSubnetFailover is used. + // connection succeeded: trigger exception if server sends failover partner and MultiSubnetFailover is used throw SQL.MultiSubnetFailoverWithFailoverPartner(serverProvidedFailoverPartner: true, internalConnection: this); } - if (_routingInfo != null) + if (RoutingInfo != null) { SqlClientEventSource.Log.TryTraceEvent(" Routed to {0}", serverInfo.ExtendedServerName); - - if (routingAttempts > _maxNumberOfRedirectRoute) + if (routingAttempts > MaxNumberOfRedirectRoute) { - throw SQL.ROR_RecursiveRoutingNotSupported(this, _maxNumberOfRedirectRoute); + throw SQL.ROR_RecursiveRoutingNotSupported(this, MaxNumberOfRedirectRoute); } if (timeout.IsExpired) @@ -1880,7 +1690,7 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt throw SQL.ROR_TimeoutAfterRoutingInfo(this); } - serverInfo = new ServerInfo(ConnectionOptions, _routingInfo, serverInfo.ResolvedServerName, serverInfo.ServerSPN); + serverInfo = new ServerInfo(ConnectionOptions, RoutingInfo, serverInfo.ResolvedServerName, serverInfo.ServerSPN); _timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination); _originalClientConnectionId = _clientConnectionId; _routingDestination = serverInfo.UserServerName; @@ -1912,8 +1722,9 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt || TdsParserState.Closed != _parser.State || IsDoNotRetryConnectError(sqlex) || timeout.IsExpired) - { // no more time to try again - throw; // Caller will call LoginFailure() + { + // no more time to try again + throw; // Caller will call LoginFailure() } // Check sleep interval to make sure we won't exceed the timeout @@ -1922,8 +1733,6 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt { throw; } - - // TODO: Stash parser away somewhere so we can examine it's state during debugging } // We only get here when we failed to connect, but are going to re-try @@ -1958,7 +1767,6 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt // Sleep for a bit to prevent clogging the network with requests, // then update sleep interval for next iteration (max 1 second interval) SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, sleeping {1}[milisec]", ObjectID, sleepInterval); - Thread.Sleep(sleepInterval); sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000; } @@ -2043,7 +1851,6 @@ private void LoginWithFailover( TimeoutTimer timeout ) { - Debug.Assert(!connectionOptions.MultiSubnetFailover, "MultiSubnetFailover should not be set if failover partner is used"); SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, useFailover={1}[bool], primary={2}, failover={3}", ObjectID, useFailoverHost, primaryServerInfo.UserServerName, failoverHost); @@ -2055,8 +1862,7 @@ TimeoutTimer timeout ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions); if (ServerProvidedFailOverPartner == null) - {// No point in resolving the failover partner when we're going to override it below - // Don't resolve aliases if failover == primary // UNDONE: WHY? Previous code in TdsParser.Connect did this, but the reason is not clear + { ResolveExtendedServerName(failoverServerInfo, !redirectedUserInstance && failoverHost != primaryServerInfo.UserServerName, connectionOptions); } @@ -2101,7 +1907,7 @@ TimeoutTimer timeout } _parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous); - Debug.Assert(SniContext.Undefined == Parser._physicalStateObj.SniContext, string.Format((IFormatProvider)null, "SniContext should be Undefined; actual Value: {0}", Parser._physicalStateObj.SniContext)); + Debug.Assert(SniContext.Undefined == Parser._physicalStateObj.SniContext, $"SniContext should be Undefined; actual Value: {Parser._physicalStateObj.SniContext}"); ServerInfo currentServerInfo; if (useFailoverHost) @@ -2139,33 +1945,34 @@ TimeoutTimer timeout ); int routingAttempts = 0; - while (_routingInfo != null) + while (RoutingInfo != null) { - if (routingAttempts > _maxNumberOfRedirectRoute) + if (routingAttempts > MaxNumberOfRedirectRoute) { - throw SQL.ROR_RecursiveRoutingNotSupported(this, _maxNumberOfRedirectRoute); + throw SQL.ROR_RecursiveRoutingNotSupported(this, MaxNumberOfRedirectRoute); } routingAttempts++; - SqlClientEventSource.Log.TryTraceEvent(" Routed to {0}", _routingInfo.ServerName); + SqlClientEventSource.Log.TryTraceEvent(" Routed to {0}", RoutingInfo.ServerName); if (_parser != null) { _parser.Disconnect(); } - _parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous); + _parser = new TdsParser(ConnectionOptions.MARS, connectionOptions.Asynchronous); + Debug.Assert(SniContext.Undefined == Parser._physicalStateObj.SniContext, $"SniContext should be Undefined; actual Value: {Parser._physicalStateObj.SniContext}"); - currentServerInfo = new ServerInfo(ConnectionOptions, _routingInfo, currentServerInfo.ResolvedServerName, currentServerInfo.ServerSPN); + currentServerInfo = new ServerInfo(ConnectionOptions, RoutingInfo, currentServerInfo.ResolvedServerName, currentServerInfo.ServerSPN); _timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination); _originalClientConnectionId = _clientConnectionId; _routingDestination = currentServerInfo.UserServerName; // restore properties that could be changed by the environment tokens - _currentPacketSize = ConnectionOptions.PacketSize; + _currentPacketSize = connectionOptions.PacketSize; _currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage; - CurrentDatabase = _originalDatabase = ConnectionOptions.InitialCatalog; + CurrentDatabase = _originalDatabase = connectionOptions.InitialCatalog; _currentFailoverPartner = null; _instanceName = string.Empty; @@ -2174,8 +1981,7 @@ TimeoutTimer timeout newPassword, newSecurePassword, intervalTimer, - withFailover: true - ); + withFailover: true); } break; // leave the while loop -- we've successfully connected @@ -2207,8 +2013,6 @@ TimeoutTimer timeout throw; } } - - // TODO: Stash parser away somewhere so we can examine it's state during debugging } // We only get here when we failed to connect, but are going to re-try @@ -2290,11 +2094,16 @@ private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup, } // Common code path for making one attempt to establish a connection and log in to server. - private void AttemptOneLogin(ServerInfo serverInfo, string newPassword, SecureString newSecurePassword, TimeoutTimer timeout, bool withFailover = false, bool isFirstTransparentAttempt = true, bool disableTnir = false) + private void AttemptOneLogin(ServerInfo serverInfo, + string newPassword, + SecureString newSecurePassword, + TimeoutTimer timeout, + bool withFailover = false, + bool isFirstTransparentAttempt = true, + bool disableTnir = false) { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, timeout={1}[msec], server={2}", ObjectID, timeout.MillisecondsRemaining, serverInfo.ExtendedServerName); - - _routingInfo = null; // forget routing information + RoutingInfo = null; // forget routing information _parser._physicalStateObj.SniContext = SniContext.Snix_Connect; @@ -2320,7 +2129,6 @@ private void AttemptOneLogin(ServerInfo serverInfo, string newPassword, SecureSt _timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin); } - internal void FailoverPermissionDemand() { if (PoolGroupProviderInfo != null) @@ -2354,11 +2162,10 @@ protected override void ReleaseAdditionalLocksForClose(bool lockToken) } } - // called by SqlConnection.RepairConnection which is a relatevly expensive way of repair inner connection + // called by SqlConnection.RepairConnection which is a relatively expensive way of repair inner connection // prior to execution of request, used from EnlistTransaction, EnlistDistributedTransaction and ChangeDatabase internal bool GetSessionAndReconnectIfNeeded(SqlConnection parent, int timeout = 0) { - Debug.Assert(!ThreadHasParserLockForClose, "Cannot call this method if caller has parser lock"); if (ThreadHasParserLockForClose) { @@ -2374,32 +2181,18 @@ internal bool GetSessionAndReconnectIfNeeded(SqlConnection parent, int timeout = RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - RuntimeHelpers.PrepareConstrainedRegions(); - try + Task reconnectTask = parent.ValidateAndReconnect(() => { - tdsReliabilitySection.Start(); -#endif //DEBUG - Task reconnectTask = parent.ValidateAndReconnect(() => - { - ThreadHasParserLockForClose = false; - _parserLock.Release(); - releaseConnectionLock = false; - }, timeout); - if (reconnectTask != null) - { - AsyncHelper.WaitForCompletion(reconnectTask, timeout); - return true; - } - return false; -#if DEBUG - } - finally + ThreadHasParserLockForClose = false; + _parserLock.Release(); + releaseConnectionLock = false; + }, timeout); + if (reconnectTask != null) { - tdsReliabilitySection.Stop(); + AsyncHelper.WaitForCompletion(reconnectTask, timeout); + return true; } -#endif //DEBUG + return false; } catch (System.OutOfMemoryException) { @@ -2436,7 +2229,6 @@ internal void BreakConnection() SqlConnection connection = Connection; SqlClientEventSource.Log.TryTraceEvent(" {0}, Breaking connection.", ObjectID); DoomThisConnection(); // Mark connection as unusable, so it will be destroyed - if (connection != null) { connection.Close(); @@ -2447,7 +2239,7 @@ internal bool IgnoreEnvChange { // true if we are only draining environment change tokens, used by TdsParser get { - return _routingInfo != null; // connection was routed, ignore rest of env change + return RoutingInfo != null; // connection was routed, ignore rest of env change } } @@ -2457,7 +2249,7 @@ internal void OnEnvChange(SqlEnvChange rec) switch (rec._type) { case TdsEnums.ENV_DATABASE: - // If connection is not open and recovery is not in progresss, store the server value as the original. + // If connection is not open and recovery is not in progress, store the server value as the original. if (!_fConnectionOpen && _recoverySessionData == null) { _originalDatabase = rec._newValue; @@ -2467,13 +2259,13 @@ internal void OnEnvChange(SqlEnvChange rec) break; case TdsEnums.ENV_LANG: - // If connection is not open and recovery is not in progresss, store the server value as the original. + // If connection is not open and recovery is not in progress, store the server value as the original. if (!_fConnectionOpen && _recoverySessionData == null) { _originalLanguage = rec._newValue; } - _currentLanguage = rec._newValue; // TODO: finish this. + _currentLanguage = rec._newValue; break; case TdsEnums.ENV_PACKETSIZE: @@ -2542,7 +2334,7 @@ internal void OnEnvChange(SqlEnvChange rec) { throw SQL.ROR_InvalidRoutingInfo(this); } - _routingInfo = rec._newRoutingInfo; + RoutingInfo = rec._newRoutingInfo; break; default: @@ -2554,7 +2346,6 @@ internal void OnEnvChange(SqlEnvChange rec) internal void OnLoginAck(SqlLoginAck rec) { _loginAck = rec; - // UNDONE: throw an error if this is not 7.0 or 7.1[5]. if (_recoverySessionData != null) { if (_recoverySessionData._tdsVersion != rec.tdsVersion) @@ -2578,11 +2369,11 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) || _credential != null || _accessTokenCallback != null || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive + || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity - || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired), "Credentials aren't provided for calling MSAL"); Debug.Assert(fedAuthInfo != null, "info should not be null."); @@ -2656,7 +2447,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) Debug.Assert(!attemptRefreshTokenLocked || _fedAuthToken != null, "Either Lock should not have been obtained or _fedAuthToken should not be null."); Debug.Assert(!attemptRefreshTokenLocked || _newDbConnectionPoolAuthenticationContext != null, "Either Lock should not have been obtained or _newDbConnectionPoolAuthenticationContext should not be null."); - // Indicate in Bid Trace that we are successful with the update. + // Indicate in EventSource Trace that we are successful with the update. if (attemptRefreshTokenLocked) { SqlClientEventSource.Log.TryTraceEvent(" {0}, The attempt to get a new access token succeeded under the locked mode.", ObjectID); @@ -2767,7 +2558,6 @@ internal bool TryGetFedAuthTokenLocked(SqlFedAuthInfo fedAuthInfo, DbConnectionP /// SqlFedAuthToken internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { - Debug.Assert(fedAuthInfo != null, "fedAuthInfo should not be null."); // No:of milliseconds to sleep for the inital back off. @@ -2938,9 +2728,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) // Deal with normal MsalExceptions. catch (MsalException msalException) { - if (MsalError.UnknownError != msalException.ErrorCode - || _timeout.IsExpired - || _timeout.MillisecondsRemaining <= sleepInterval) + if (MsalError.UnknownError != msalException.ErrorCode || _timeout.IsExpired || _timeout.MillisecondsRemaining <= sleepInterval) { SqlClientEventSource.Log.TryTraceEvent(" {0}", msalException.ErrorCode); @@ -2956,7 +2744,21 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) // All other exceptions from MSAL/Azure Identity APIs catch (Exception e) { - throw SqlException.CreateException(new() { new(0, (byte)0x00, (byte)TdsEnums.FATAL_ERROR_CLASS, ConnectionOptions.DataSource, e.Message, ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0) }, "", this, e); + throw SqlException.CreateException( + new() + { + new( + 0, + (byte)0x00, + (byte)TdsEnums.FATAL_ERROR_CLASS, + ConnectionOptions.DataSource, + e.Message, + ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, + 0) + }, + "", + this, + e); } } @@ -2975,12 +2777,9 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) internal void OnFeatureExtAck(int featureId, byte[] data) { - if (_routingInfo != null) + if (RoutingInfo != null && featureId != TdsEnums.FEATUREEXT_SQLDNSCACHING) { - if (TdsEnums.FEATUREEXT_SQLDNSCACHING != featureId) - { - return; - } + return; } switch (featureId) @@ -3034,6 +2833,25 @@ internal void OnFeatureExtAck(int featureId, byte[] data) } break; } + + case TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS: + { + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for GlobalTransactions", ObjectID); + + if (data.Length < 1) + { + SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown version number for GlobalTransactions", ObjectID); + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); + } + + IsGlobalTransaction = true; + if (1 == data[0]) + { + IsGlobalTransactionsEnabledForServer = true; + } + break; + } + case TdsEnums.FEATUREEXT_FEDAUTH: { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for federated authentication", ObjectID); @@ -3088,7 +2906,6 @@ internal void OnFeatureExtAck(int featureId, byte[] data) } #endif } - break; } case TdsEnums.FEATUREEXT_TCE: @@ -3115,27 +2932,9 @@ internal void OnFeatureExtAck(int featureId, byte[] data) if (data.Length > 1) { + // Extract the type of enclave being used by the server. _parser.EnclaveType = Encoding.Unicode.GetString(data, 2, (data.Length - 2)); } - - break; - } - - case TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS: - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for GlobalTransactions", ObjectID); - - if (data.Length < 1) - { - SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown version number for GlobalTransactions", ObjectID); - throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); - } - - IsGlobalTransaction = true; - if (1 == data[0]) - { - IsGlobalTransactionsEnabledForServer = true; - } break; } @@ -3165,7 +2964,6 @@ internal void OnFeatureExtAck(int featureId, byte[] data) if (data.Length < 1) { SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown token for DATACLASSIFICATION", ObjectID); - throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); } byte supportedDataClassificationVersion = data[0]; @@ -3212,7 +3010,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data) IsSQLDNSCachingSupported = true; _cleanSQLDNSCaching = false; - if (_routingInfo != null) + if (RoutingInfo != null) { IsDNSCachingBeforeRedirectSupported = true; } @@ -3224,12 +3022,10 @@ internal void OnFeatureExtAck(int featureId, byte[] data) _cleanSQLDNSCaching = true; } - // need to add more steps for phrase 2 + // need to add more steps for phase 2 // get IPv4 + IPv6 + Port number // not put them in the DNS cache at this point but need to store them somewhere - // generate pendingSQLDNSObject and turn on IsSQLDNSRetryEnabled flag - break; } @@ -3251,6 +3047,24 @@ internal void OnFeatureExtAck(int featureId, byte[] data) break; } + case TdsEnums.FEATUREEXT_VECTORSUPPORT: + { + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for VECTORSUPPORT", ObjectID); + if (data.Length != 1) + { + SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown token for VECTORSUPPORT", ObjectID); + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); + } + byte vectorSupportVersion = data[0]; + if (vectorSupportVersion == 0 || vectorSupportVersion > TdsEnums.MAX_SUPPORTED_VECTOR_VERSION) + { + SqlClientEventSource.Log.TryTraceEvent(" {0}, Invalid version number {1} for VECTORSUPPORT, Max supported version is {2}", ObjectID, vectorSupportVersion, TdsEnums.MAX_SUPPORTED_VECTOR_VERSION); + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); + } + IsVectorSupportEnabled = true; + break; + } + default: { // Unknown feature ack @@ -3310,14 +3124,14 @@ internal string UserServerName { get { - return m_userServerName; + return _userServerName; } private set { - m_userServerName = value; + _userServerName = value; } } - private string m_userServerName; + private string _userServerName; internal readonly string PreRoutingServerName; @@ -3381,7 +3195,7 @@ internal void SetDerivedNames(string protocol, string serverName) // when using the Dbnetlib dll. If the protocol is not specified, the netlib will // try all protocols in the order listed in the Client Network Utility. Connect will // then fail if all protocols fail. - if (!ADP.IsEmpty(protocol)) + if (!string.IsNullOrEmpty(protocol)) { ExtendedServerName = protocol + ":" + serverName; } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlSequentialStreamSmi.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlSequentialStreamSmi.cs deleted file mode 100644 index 9705d32abc..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlSequentialStreamSmi.cs +++ /dev/null @@ -1,125 +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 Microsoft.Data.Common; -using Microsoft.Data.SqlClient.Server; - -namespace Microsoft.Data.SqlClient -{ - sealed internal class SqlSequentialStreamSmi : System.IO.Stream - { - private SmiEventSink_Default _sink; - private ITypedGettersV3 _getters; - private int _columnIndex; // The index of out column in the table - private long _position; // Current position in the stream - private long _length; // Total length of the stream - - internal SqlSequentialStreamSmi(SmiEventSink_Default sink, ITypedGettersV3 getters, int columnIndex, long length) - { - _sink = sink; - _getters = getters; - _columnIndex = columnIndex; - _length = length; - _position = 0; - } - - public override bool CanRead - { - get { return ((_sink != null) && (_getters != null)); } - } - - public override bool CanSeek - { - get { return false; } - } - - public override bool CanWrite - { - get { return false; } - } - - public override void Flush() - { } - - public override long Length - { - get { throw ADP.NotSupported(); } - } - - public override long Position - { - get { throw ADP.NotSupported(); } - set { throw ADP.NotSupported(); } - } - - internal int ColumnIndex - { - get { return _columnIndex; } - } - - public override int Read(byte[] buffer, int offset, int count) - { - SqlSequentialStream.ValidateReadParameters(buffer, offset, count); - if (!CanRead) - { - throw ADP.ObjectDisposed(this); - } - - try - { - // Read whichever is less: however much the user asked for, or however much we have - // NOTE: It is safe to do this since count <= Int32.MaxValue, therefore the Math.Min should always result in an int - int bytesNeeded = (int)Math.Min((long)count, _length - _position); - int bytesRead = 0; - if (bytesNeeded > 0) - { - bytesRead = ValueUtilsSmi.GetBytes_Unchecked(_sink, _getters, _columnIndex, _position, buffer, offset, bytesNeeded); - _position += bytesRead; - } - return bytesRead; - } - catch (SqlException ex) - { - // Stream.Read() can't throw a SqlException - so wrap it in an IOException - throw ADP.ErrorReadingFromStream(ex); - } - } - - public override long Seek(long offset, System.IO.SeekOrigin origin) - { - throw ADP.NotSupported(); - } - - public override void SetLength(long value) - { - throw ADP.NotSupported(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - throw ADP.NotSupported(); - } - - /// - /// Forces the stream to act as if it was closed (i.e. CanRead=false and Read() throws) - /// This does not actually close the stream, read off the rest of the data or dispose this - /// - internal void SetClosed() - { - _sink = null; - _getters = null; - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - SetClosed(); - } - - base.Dispose(disposing); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlSequentialTextReaderSmi.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlSequentialTextReaderSmi.cs deleted file mode 100644 index 7adbb67f21..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlSequentialTextReaderSmi.cs +++ /dev/null @@ -1,137 +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 Microsoft.Data.Common; -using Microsoft.Data.SqlClient.Server; - -namespace Microsoft.Data.SqlClient -{ - sealed internal class SqlSequentialTextReaderSmi : System.IO.TextReader - { - private SmiEventSink_Default _sink; - private ITypedGettersV3 _getters; - private int _columnIndex; // The index of out column in the table - private long _position; // Current position in the stream - private long _length; // Total length of the stream - private int _peekedChar; // Current peeked character (if any) - - internal SqlSequentialTextReaderSmi(SmiEventSink_Default sink, ITypedGettersV3 getters, int columnIndex, long length) - { - _sink = sink; - _getters = getters; - _columnIndex = columnIndex; - _length = length; - _position = 0; - _peekedChar = -1; - } - - internal int ColumnIndex - { - get { return _columnIndex; } - } - - public override int Peek() - { - if (!HasPeekedChar) - { - _peekedChar = Read(); - } - - Debug.Assert(_peekedChar == -1 || ((_peekedChar >= char.MinValue) && (_peekedChar <= char.MaxValue)), string.Format("Bad peeked character: {0}", _peekedChar)); - return _peekedChar; - } - - public override int Read() - { - if (IsClosed) - { - throw ADP.ObjectDisposed(this); - } - - int readChar = -1; - - // If there is already a peeked char, then return it - if (HasPeekedChar) - { - readChar = _peekedChar; - _peekedChar = -1; - } - // If there is data available try to read a char - else if (_position < _length) - { - char[] tempBuffer = new char[1]; - int charsRead = ValueUtilsSmi.GetChars_Unchecked(_sink, _getters, _columnIndex, _position, tempBuffer, 0, 1); - if (charsRead == 1) - { - readChar = tempBuffer[0]; - _position++; - } - } - - Debug.Assert(readChar == -1 || ((readChar >= char.MinValue) && (readChar <= char.MaxValue)), string.Format("Bad read character: {0}", readChar)); - return readChar; - } - - public override int Read(char[] buffer, int index, int count) - { - SqlSequentialTextReader.ValidateReadParameters(buffer, index, count); - if (IsClosed) - { - throw ADP.ObjectDisposed(this); - } - - int charsRead = 0; - // Load in peeked char - if ((count > 0) && (HasPeekedChar)) - { - Debug.Assert((_peekedChar >= char.MinValue) && (_peekedChar <= char.MaxValue), string.Format("Bad peeked character: {0}", _peekedChar)); - buffer[index + charsRead] = (char)_peekedChar; - charsRead++; - _peekedChar = -1; - } - - // Read whichever is less: however much the user asked for, or however much we have - // NOTE: It is safe to do this since count <= Int32.MaxValue, therefore the Math.Min should always result in an int - int charsNeeded = (int)Math.Min((long)(count - charsRead), _length - _position); - // If we need more data and there is data avaiable, read - if (charsNeeded > 0) - { - int newCharsRead = ValueUtilsSmi.GetChars_Unchecked(_sink, _getters, _columnIndex, _position, buffer, index + charsRead, charsNeeded); - _position += newCharsRead; - charsRead += newCharsRead; - } - - return charsRead; - } - - /// - /// Forces the TextReader to act as if it was closed - /// This does not actually close the stream, read off the rest of the data or dispose this - /// - internal void SetClosed() - { - _sink = null; - _getters = null; - _peekedChar = -1; - } - - /// - /// True if this TextReader is supposed to be closed - /// - private bool IsClosed - { - get { return ((_sink == null) || (_getters == null)); } - } - - /// - /// True if there is a peeked character available - /// - private bool HasPeekedChar - { - get { return (_peekedChar >= char.MinValue); } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlTransaction.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlTransaction.cs deleted file mode 100644 index d5a93b1b86..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlTransaction.cs +++ /dev/null @@ -1,343 +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.ComponentModel; -using System.Data.Common; -using System.Runtime.CompilerServices; -using Microsoft.Data.Common; - -namespace Microsoft.Data.SqlClient -{ - /// - public sealed partial class SqlTransaction : DbTransaction - { - //////////////////////////////////////////////////////////////////////////////////////// - // PUBLIC METHODS - //////////////////////////////////////////////////////////////////////////////////////// - - /// - public override void Commit() - { - SqlConnection.ExecutePermission.Demand(); // MDAC 81476 - - ZombieCheck(); - - SqlStatistics statistics = null; - using (TryEventScope.Create(" {0}", ObjectID)) - { - SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current); - - TdsParser bestEffortCleanupTarget = null; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); - statistics = SqlStatistics.StartTimer(Statistics); - - _isFromAPI = true; - - _internalTransaction.Commit(); - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - _connection.Abort(e); - throw; - } - catch (System.StackOverflowException e) - { - _connection.Abort(e); - throw; - } - catch (System.Threading.ThreadAbortException e) - { - _connection.Abort(e); - SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); - throw; - } - catch (SqlException e) - { - // GitHub Issue #130 - When a timeout exception has occurred on transaction completion request, - // this connection may not be in reusable state. - // We will abort this connection and make sure it does not go back to the pool. - if (e.InnerException is Win32Exception innerException && innerException.NativeErrorCode == TdsEnums.SNI_WAIT_TIMEOUT) - { - _connection.Abort(e); - } - throw; - } - finally - { - _isFromAPI = false; - - SqlStatistics.StopTimer(statistics); - } - } - } - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - TdsParser bestEffortCleanupTarget = null; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); - if (!IsZombied && !Is2005PartialZombie) - { - _internalTransaction.Dispose(); - } - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - _connection.Abort(e); - throw; - } - catch (System.StackOverflowException e) - { - _connection.Abort(e); - throw; - } - catch (System.Threading.ThreadAbortException e) - { - _connection.Abort(e); - SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); - throw; - } - } - base.Dispose(disposing); - } - - /// - public override void Rollback() - { - if (Is2005PartialZombie) - { - // Put something in the trace in case a customer has an issue - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} partial zombie no rollback required", ObjectID); - - _internalTransaction = null; // 2005 zombification - } - else - { - ZombieCheck(); - - SqlStatistics statistics = null; - using (TryEventScope.Create(" {0}", ObjectID)) - { - SqlClientEventSource.Log.TryCorrelationTraceEvent(" ObjectID {0}, ActivityID {1}", ObjectID, ActivityCorrelator.Current); - - TdsParser bestEffortCleanupTarget = null; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); - statistics = SqlStatistics.StartTimer(Statistics); - - _isFromAPI = true; - - _internalTransaction.Rollback(); - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - _connection.Abort(e); - throw; - } - catch (System.StackOverflowException e) - { - _connection.Abort(e); - throw; - } - catch (System.Threading.ThreadAbortException e) - { - _connection.Abort(e); - SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); - throw; - } - finally - { - _isFromAPI = false; - - SqlStatistics.StopTimer(statistics); - } - } - } - } - - /// - public void Rollback(string transactionName) - { - SqlConnection.ExecutePermission.Demand(); // MDAC 81476 - - ZombieCheck(); - - SqlStatistics statistics = null; - using (TryEventScope.Create(" {0} transactionName='{1}'", ObjectID, transactionName)) - { - TdsParser bestEffortCleanupTarget = null; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); - statistics = SqlStatistics.StartTimer(Statistics); - - _isFromAPI = true; - - _internalTransaction.Rollback(transactionName); - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - _connection.Abort(e); - throw; - } - catch (System.StackOverflowException e) - { - _connection.Abort(e); - throw; - } - catch (System.Threading.ThreadAbortException e) - { - _connection.Abort(e); - SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); - throw; - } - finally - { - _isFromAPI = false; - - SqlStatistics.StopTimer(statistics); - } - } - } - - /// - public void Save(string savePointName) - { - SqlConnection.ExecutePermission.Demand(); // MDAC 81476 - - ZombieCheck(); - - SqlStatistics statistics = null; - using (TryEventScope.Create(" {0} savePointName='{1}'", ObjectID, savePointName)) - { - - TdsParser bestEffortCleanupTarget = null; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); - statistics = SqlStatistics.StartTimer(Statistics); - - _internalTransaction.Save(savePointName); - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (System.OutOfMemoryException e) - { - _connection.Abort(e); - throw; - } - catch (System.StackOverflowException e) - { - _connection.Abort(e); - throw; - } - catch (System.Threading.ThreadAbortException e) - { - _connection.Abort(e); - SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); - throw; - } - finally - { - SqlStatistics.StopTimer(statistics); - } - } - } - } -} 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 03889347ae..cce4c8426e 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -4,29 +4,36 @@ using System; using System.Buffers; +using System.Buffers.Binary; using System.Collections.Generic; using System.Data; using System.Data.SqlTypes; using System.Diagnostics; using System.Globalization; using System.IO; +#if NET +using System.Security.Authentication; +#else using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Security.Cryptography.X509Certificates; +#endif using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; -using System.Net; using Interop.Common.Sni; +#if NETFRAMEWORK using Interop.Windows.Sni; +#endif using Microsoft.Data.Common; +using Microsoft.Data.ProviderBase; using Microsoft.Data.Sql; using Microsoft.Data.SqlClient.DataClassification; +using Microsoft.Data.SqlClient.LocalDb; using Microsoft.Data.SqlClient.Server; +#if NETFRAMEWORK using Microsoft.Data.SqlTypes; +#endif using Microsoft.SqlServer.Server; -using Microsoft.Data.ProviderBase; namespace Microsoft.Data.SqlClient { @@ -34,120 +41,18 @@ namespace Microsoft.Data.SqlClient // and surfacing objects to the user. internal sealed partial class TdsParser { - private static int _objectTypeCount; // EventSource Counter + private static int _objectTypeCount; // EventSource counter private readonly SqlClientLogger _logger = new SqlClientLogger(); - private SSPIContextProvider _authenticationProvider; - - internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); - - static Task completedTask; - static Task CompletedTask - { - get - { - if (completedTask == null) - { - completedTask = Task.FromResult(null); - } - return completedTask; - } - } + private SspiContextProvider _authenticationProvider; - internal int ObjectID - { - get - { - return _objectID; - } - } + internal readonly int _objectID = Interlocked.Increment(ref _objectTypeCount); + internal int ObjectID => _objectID; /// /// Verify client encryption possibility. /// - private bool ClientOSEncryptionSupport => SNILoadHandle.SingletonInstance.ClientOSEncryptionSupport; - - // ReliabilitySection Usage: - // - // #if DEBUG - // TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - // - // RuntimeHelpers.PrepareConstrainedRegions(); - // try { - // tdsReliabilitySection.Start(); - // #else - // { - // #endif //DEBUG - // - // // code that requires reliability - // - // } - // #if DEBUG - // finally { - // tdsReliabilitySection.Stop(); - // } - // #endif //DEBUG - - internal struct ReliabilitySection - { -#if DEBUG - // do not allocate TLS data in RETAIL bits - [ThreadStatic] - private static int s_reliabilityCount; // initialized to 0 by CLR - - private bool m_started; // initialized to false (not started) by CLR -#endif //DEBUG - - [Conditional("DEBUG")] - internal void Start() - { -#if DEBUG - Debug.Assert(!m_started); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - } - finally - { - ++s_reliabilityCount; - m_started = true; - } -#endif //DEBUG - } - - [Conditional("DEBUG")] - internal void Stop() - { -#if DEBUG - // cannot assert m_started - ThreadAbortException can be raised before Start is called - - if (m_started) - { - Debug.Assert(s_reliabilityCount > 0); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - } - finally - { - --s_reliabilityCount; - m_started = false; - } - } -#endif //DEBUG - } - - // you need to setup for a thread abort somewhere before you call this method - [Conditional("DEBUG")] - internal static void Assert(string message) - { -#if DEBUG - Debug.Assert(s_reliabilityCount > 0, message); -#endif //DEBUG - } - } + private bool ClientOSEncryptionSupport => TdsParserStateObjectFactory.Singleton.ClientOSEncryptionSupport; // Default state object for parser internal TdsParserStateObject _physicalStateObj = null; // Default stateObj and connection for Dbnetlib and non-MARS SNI. @@ -182,9 +87,9 @@ internal static void Assert(string message) internal Encoding _defaultEncoding = null; // for sql character data - private static EncryptionOptions _sniSupportedEncryptionOption = SNILoadHandle.SingletonInstance.Options; + private static EncryptionOptions s_sniSupportedEncryptionOption = TdsParserStateObjectFactory.Singleton.EncryptionOptions; - private EncryptionOptions _encryptionOption = _sniSupportedEncryptionOption; + private EncryptionOptions _encryptionOption = s_sniSupportedEncryptionOption; private SqlInternalTransaction _currentTransaction; private SqlInternalTransaction _pendingTransaction; // pending transaction for 2005 and beyond. @@ -209,11 +114,6 @@ internal static void Assert(string message) internal TdsParserSessionPool _sessionPool = null; // initialized only when we're a MARS parser. // Version variables - private bool _is2000 = false; // set to true if we connect to a 8.0 server (SQL 2000) or later - - private bool _is2000SP1 = false; // set to true if speaking to 2000 SP1 or later - - private bool _is2005 = false; // set to true if speaking to 2005 or later private bool _is2008 = false; @@ -221,10 +121,6 @@ internal static void Assert(string message) private bool _is2022 = false; - private byte[] _sniSpnBuffer = null; - - // UNDONE - need to have some for both instances - both command and default??? - // SqlStatistics private SqlStatistics _statistics = null; @@ -240,44 +136,27 @@ internal static void Assert(string message) // textptr sequence private static readonly byte[] s_longDataHeader = { 0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; - // Various other statics - private const int ATTENTION_TIMEOUT = 5000; // internal attention timeout, in ticks - - // XML metadata substitue sequence + // XML metadata substitute sequence private static readonly byte[] s_xmlMetadataSubstituteSequence = { 0xe7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 }; - // JSON metadata substitue sequence + // JSON metadata substitute sequence private static readonly byte[] s_jsonMetadataSubstituteSequence = { 0xa7, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 }; // size of Guid (e.g. _clientConnectionId, ActivityId.Id) private const int GUID_SIZE = 16; private byte[] _tempGuidBytes; - // NOTE: You must take the internal connection's _parserLock before modifying this - internal bool _asyncWrite = false; - - // TCE supported flag, used to determine if new TDS fields are present. This is - // useful when talking to downlevel/uplevel server. - private bool _serverSupportsColumnEncryption = false; - // now data length is 1 byte // First bit is 1 indicating client support failover partner with readonly intent - private static readonly byte[] s_FeatureExtDataAzureSQLSupportFeatureRequest = { 0x01 }; + private static readonly byte[] s_featureExtDataAzureSQLSupportFeatureRequest = { 0x01 }; + + // NOTE: You must take the internal connection's _parserLock before modifying this + internal bool _asyncWrite = false; /// /// Get or set if column encryption is supported by the server. /// - internal bool IsColumnEncryptionSupported - { - get - { - return _serverSupportsColumnEncryption; - } - set - { - _serverSupportsColumnEncryption = value; - } - } + internal bool IsColumnEncryptionSupported { get; set; } = false; /// /// TCE version supported by the server @@ -300,13 +179,8 @@ internal bool IsColumnEncryptionSupported /// /// Get if data classification is enabled by the server. /// - internal bool IsDataClassificationEnabled - { - get - { - return (DataClassificationVersion != TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED); - } - } + internal bool IsDataClassificationEnabled => + (DataClassificationVersion != TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED); /// /// Get or set data classification version. A value of 0 means that sensitivity classification is not enabled. @@ -318,7 +192,8 @@ internal bool IsDataClassificationEnabled internal TdsParser(bool MARS, bool fAsynchronous) { _fMARS = MARS; // may change during Connect to pre 2005 servers - _physicalStateObj = new TdsParserStateObject(this); + + _physicalStateObj = TdsParserStateObjectFactory.Singleton.CreateTdsParserStateObject(this); DataClassificationVersion = TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED; } @@ -357,8 +232,8 @@ internal SqlInternalTransaction CurrentTransaction // change it; this can occur when there is a delegated transaction // and the user attempts to do an API begin transaction; in these // cases, it's safe to ignore the set. - if ((_currentTransaction == null && value != null) || - (_currentTransaction != null && value == null)) + if ((_currentTransaction == null && value != null) + || (_currentTransaction != null && value == null)) { _currentTransaction = value; } @@ -385,14 +260,6 @@ internal EncryptionOptions EncryptionOptions } } - internal bool Is2005OrNewer - { - get - { - return _is2005; - } - } - internal bool Is2008OrNewer { get @@ -517,7 +384,7 @@ internal void Connect(ServerInfo serverInfo, // Clean up IsSQLDNSCachingSupported flag from previous status _connHandler.IsSQLDNSCachingSupported = false; - UInt32 sniStatus = SNILoadHandle.SingletonInstance.Status; + uint sniStatus = TdsParserStateObjectFactory.Singleton.SNIStatus; if (sniStatus != TdsEnums.SNI_SUCCESS) { @@ -527,10 +394,12 @@ internal void Connect(ServerInfo serverInfo, Debug.Fail("SNI returned status != success, but no error thrown?"); } + string serverSpn = null; + //Create LocalDB instance if necessary if (connHandler.ConnectionOptions.LocalDBInstance != null) { - LocalDBAPI.CreateLocalDBInstance(connHandler.ConnectionOptions.LocalDBInstance); + LocalDbApi.CreateLocalDbInstance(connHandler.ConnectionOptions.LocalDBInstance); if (encrypt == SqlConnectionEncryptOption.Mandatory) { // Encryption is not supported on SQL Local DB - disable it for current session. @@ -542,27 +411,24 @@ internal void Connect(ServerInfo serverInfo, // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server if (integratedSecurity || authType == SqlAuthenticationMethod.ActiveDirectoryIntegrated) { - _authenticationProvider = _physicalStateObj.CreateSSPIContextProvider(); + _authenticationProvider = _physicalStateObj.CreateSspiContextProvider(); if (!string.IsNullOrEmpty(serverInfo.ServerSPN)) { - // Native SNI requires the Unicode encoding and any other encoding like UTF8 breaks the code. - byte[] srvSPN = Encoding.Unicode.GetBytes(serverInfo.ServerSPN); - Trace.Assert(srvSPN.Length <= SNINativeMethodWrapper.SniMaxComposedSpnLength, "The provided SPN length exceeded the buffer size."); - _sniSpnBuffer = srvSPN; + serverSpn = serverInfo.ServerSPN; SqlClientEventSource.Log.TryTraceEvent(" Server SPN `{0}` from the connection string is used.", serverInfo.ServerSPN); } else { - // now allocate proper length of buffer - _sniSpnBuffer = new byte[SNINativeMethodWrapper.SniMaxComposedSpnLength]; + // Empty signifies to interop layer that SPN needs to be generated + serverSpn = string.Empty; } + SqlClientEventSource.Log.TryTraceEvent(" SSPI or Active Directory Authentication Library for SQL Server based integrated authentication"); } else { _authenticationProvider = null; - _sniSpnBuffer = null; switch (authType) { @@ -641,7 +507,7 @@ internal void Connect(ServerInfo serverInfo, serverInfo.ExtendedServerName, timeout, out instanceName, - _sniSpnBuffer, + ref serverSpn, false, true, fParallel, @@ -651,8 +517,6 @@ internal void Connect(ServerInfo serverInfo, FQDNforDNSCache, hostNameInCertificate); - _authenticationProvider?.Initialize(serverInfo, _physicalStateObj, this); - if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); @@ -680,7 +544,7 @@ internal void Connect(ServerInfo serverInfo, serverInfo.ResolvedServerName : serverInfo.PreRoutingServerName); } _state = TdsParserState.OpenNotLoggedIn; - _physicalStateObj.SniContext = SniContext.Snix_PreLoginBeforeSuccessfulWrite; // SQL BU DT 376766 + _physicalStateObj.SniContext = SniContext.Snix_PreLoginBeforeSuccessfulWrite; _physicalStateObj.TimeoutTime = timeout.LegacyTimerExpire; bool marsCapable = false; @@ -688,7 +552,7 @@ internal void Connect(ServerInfo serverInfo, _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.InitializeConnection); _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake); - UInt32 result = SNINativeMethodWrapper.SniGetConnectionId(_physicalStateObj.Handle, ref _connHandler._clientConnectionId); + uint result = _physicalStateObj.SniGetConnectionId(ref _connHandler._clientConnectionId); Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); // for DNS Caching phase 1 @@ -708,12 +572,7 @@ internal void Connect(ServerInfo serverInfo, // UNDONE - send "" for instance now, need to fix later SqlClientEventSource.Log.TryTraceEvent(" Sending prelogin handshake"); - - SendPreLoginHandshake( - instanceName, - encrypt, - integratedSecurity, - serverCertificateFilename); + SendPreLoginHandshake(instanceName, encrypt, integratedSecurity, serverCertificateFilename); _connHandler.TimeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake); _connHandler.TimeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake); @@ -721,7 +580,6 @@ internal void Connect(ServerInfo serverInfo, _physicalStateObj.SniContext = SniContext.Snix_PreLogin; SqlClientEventSource.Log.TryTraceEvent(" Consuming prelogin handshake"); PreLoginHandshakeStatus status = ConsumePreLoginHandshake( - authType, encrypt, trustServerCert, integratedSecurity, @@ -741,7 +599,7 @@ internal void Connect(ServerInfo serverInfo, serverInfo.ExtendedServerName, timeout, out instanceName, - _sniSpnBuffer, + ref serverSpn, true, true, fParallel, @@ -751,8 +609,6 @@ internal void Connect(ServerInfo serverInfo, serverInfo.ResolvedServerName, hostNameInCertificate); - _authenticationProvider?.Initialize(serverInfo, _physicalStateObj, this); - if (TdsEnums.SNI_SUCCESS != _physicalStateObj.Status) { _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); @@ -760,21 +616,15 @@ internal void Connect(ServerInfo serverInfo, ThrowExceptionAndWarning(_physicalStateObj); } - UInt32 retCode = SNINativeMethodWrapper.SniGetConnectionId(_physicalStateObj.Handle, ref _connHandler._clientConnectionId); + uint retCode = _physicalStateObj.SniGetConnectionId(ref _connHandler._clientConnectionId); Debug.Assert(retCode == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionId"); SqlClientEventSource.Log.TryTraceEvent(" Sending prelogin handshake"); // for DNS Caching phase 1 AssignPendingDNSInfo(serverInfo.UserProtocol, FQDNforDNSCache); - SendPreLoginHandshake( - instanceName, - encrypt, - integratedSecurity, - serverCertificateFilename); - + SendPreLoginHandshake(instanceName, encrypt, integratedSecurity, serverCertificateFilename); status = ConsumePreLoginHandshake( - authType, encrypt, trustServerCert, integratedSecurity, @@ -793,6 +643,8 @@ internal void Connect(ServerInfo serverInfo, } SqlClientEventSource.Log.TryTraceEvent(" Prelogin handshake successful"); + _authenticationProvider?.Initialize(serverInfo, _physicalStateObj, this, serverSpn); + if (_fMARS && marsCapable) { // if user explicitly disables mars or mars not supported, don't create the session pool @@ -805,68 +657,12 @@ internal void Connect(ServerInfo serverInfo, return; } - // Retrieve the IP and port number from native SNI for TCP protocol. The IP information is stored temporarily in the - // pendingSQLDNSObject but not in the DNS Cache at this point. We only add items to the DNS Cache after we receive the - // IsSupported flag as true in the feature ext ack from server. - internal void AssignPendingDNSInfo(string userProtocol, string DNSCacheKey) - { - UInt32 result; - ushort portFromSNI = 0; - string IPStringFromSNI = string.Empty; - IPAddress IPFromSNI; - isTcpProtocol = false; - Provider providerNumber = Provider.INVALID_PROV; - - if (string.IsNullOrEmpty(userProtocol)) - { - - result = SNINativeMethodWrapper.SniGetProviderNumber(_physicalStateObj.Handle, ref providerNumber); - Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetProviderNumber"); - isTcpProtocol = (providerNumber == Provider.TCP_PROV); - } - else if (userProtocol == TdsEnums.TCP) - { - isTcpProtocol = true; - } - - // serverInfo.UserProtocol could be empty - if (isTcpProtocol) - { - result = SNINativeMethodWrapper.SniGetConnectionPort(_physicalStateObj.Handle, ref portFromSNI); - Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionPort"); - - - result = SNINativeMethodWrapper.SniGetConnectionIPString(_physicalStateObj.Handle, ref IPStringFromSNI); - Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionIPString"); - - _connHandler.pendingSQLDNSObject = new SQLDNSInfo(DNSCacheKey, null, null, portFromSNI.ToString()); - - if (IPAddress.TryParse(IPStringFromSNI, out IPFromSNI)) - { - if (System.Net.Sockets.AddressFamily.InterNetwork == IPFromSNI.AddressFamily) - { - _connHandler.pendingSQLDNSObject.AddrIPv4 = IPStringFromSNI; - } - else if (System.Net.Sockets.AddressFamily.InterNetworkV6 == IPFromSNI.AddressFamily) - { - _connHandler.pendingSQLDNSObject.AddrIPv6 = IPStringFromSNI; - } - } - } - else - { - _connHandler.pendingSQLDNSObject = null; - } - } - internal void RemoveEncryption() { Debug.Assert((_encryptionOption & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.LOGIN, "Invalid encryption option state"); - UInt32 error = 0; + uint error = _physicalStateObj.DisableSsl(); - // Remove SSL (Encryption) SNI provider since we only wanted to encrypt login. - error = SNINativeMethodWrapper.SNIRemoveProvider(_physicalStateObj.Handle, Provider.SSL_PROV); if (error != TdsEnums.SNI_SUCCESS) { _physicalStateObj.AddError(ProcessSNIError(_physicalStateObj)); @@ -889,11 +685,8 @@ internal void EnableMars() // Cache physical stateObj and connection. _pMarsPhysicalConObj = _physicalStateObj; - UInt32 error = 0; - UInt32 info = 0; - - // Add SMUX (MARS) SNI provider. - error = SNINativeMethodWrapper.SNIAddProvider(_pMarsPhysicalConObj.Handle, Provider.SMUX_PROV, ref info); + uint info = 0; + uint error = _pMarsPhysicalConObj.EnableMars(ref info); if (error != TdsEnums.SNI_SUCCESS) { @@ -914,12 +707,12 @@ internal void EnableMars() { _pMarsPhysicalConObj.IncrementPendingCallbacks(); - error = SNINativeMethodWrapper.SNIReadAsync(_pMarsPhysicalConObj.Handle, ref temp); + error = SniNativeWrapper.SniReadAsync(_pMarsPhysicalConObj.Handle, ref temp); if (temp != IntPtr.Zero) { // Be sure to release packet, otherwise it will be leaked by native. - SNINativeMethodWrapper.SNIPacketRelease(temp); + SniNativeWrapper.SniPacketRelease(temp); } } Debug.Assert(IntPtr.Zero == temp, "unexpected syncReadPacket without corresponding SNIPacketRelease"); @@ -936,7 +729,7 @@ internal void EnableMars() internal TdsParserStateObject CreateSession() { - TdsParserStateObject session = new TdsParserStateObject(this, (SNIHandle)_pMarsPhysicalConObj.Handle, true); + TdsParserStateObject session = TdsParserStateObjectFactory.Singleton.CreateSessionObject(this, _pMarsPhysicalConObj, true); SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} created session {1}", ObjectID, session.ObjectID); return session; } @@ -989,53 +782,18 @@ internal void PutSession(TdsParserStateObject session) } } - // This is called from a ThreadAbort - ensure that it can be run from a CER Catch - internal void BestEffortCleanup() - { - _state = TdsParserState.Broken; - - var stateObj = _physicalStateObj; - if (stateObj != null) - { - var stateObjHandle = stateObj.Handle; - if (stateObjHandle != null) - { - stateObjHandle.Dispose(); - } - } - - if (_fMARS) - { - var sessionPool = _sessionPool; - if (sessionPool != null) - { - sessionPool.BestEffortCleanup(); - } - - var marsStateObj = _pMarsPhysicalConObj; - if (marsStateObj != null) - { - var marsStateObjHandle = marsStateObj.Handle; - if (marsStateObjHandle != null) - { - marsStateObjHandle.Dispose(); - } - } - } - } - private void SendPreLoginHandshake( byte[] instanceName, SqlConnectionEncryptOption encrypt, bool integratedSecurity, - string serverCertificate) + string serverCertificateFilename) { if (encrypt == SqlConnectionEncryptOption.Strict) { //Always validate the certificate when in strict encryption mode uint info = TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE | TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE | TdsEnums.SNI_SSL_SEND_ALPN_EXTENSION; - EnableSsl(info, encrypt, integratedSecurity, serverCertificate); + EnableSsl(info, encrypt, integratedSecurity, serverCertificateFilename); // Since encryption has already been negotiated, we need to set encryption not supported in // prelogin so that we don't try to negotiate encryption again during ConsumePreLoginHandshake. @@ -1138,7 +896,7 @@ private void SendPreLoginHandshake( break; case (int)PreLoginOptions.THREADID: - Int32 threadID = TdsParserStaticMethods.GetCurrentThreadIdForTdsLoginOnly(); + int threadID = TdsParserStaticMethods.GetCurrentThreadIdForTdsLoginOnly(); payload[payloadLength++] = (byte)((0xff000000 & threadID) >> 24); payload[payloadLength++] = (byte)((0x00ff0000 & threadID) >> 16); @@ -1170,7 +928,7 @@ private void SendPreLoginHandshake( payload[payloadLength++] = (byte)((0x0000ff00 & actId.Sequence) >> 8); payload[payloadLength++] = (byte)((0x00ff0000 & actId.Sequence) >> 16); payload[payloadLength++] = (byte)((0xff000000 & actId.Sequence) >> 24); - int actIdSize = GUID_SIZE + sizeof(UInt32); + int actIdSize = GUID_SIZE + sizeof(uint); offset += actIdSize; optionDataSize += actIdSize; @@ -1203,7 +961,7 @@ private void SendPreLoginHandshake( _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); } - private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integratedSecurity, string serverCertificate) + private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integratedSecurity, string serverCertificateFilename) { uint error = 0; @@ -1223,11 +981,11 @@ private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integ authInfo.certHash = false; authInfo.clientCertificateCallbackContext = IntPtr.Zero; authInfo.clientCertificateCallback = null; - authInfo.serverCertFileName = string.IsNullOrEmpty(serverCertificate) ? null : serverCertificate; + authInfo.serverCertFileName = string.IsNullOrEmpty(serverCertificateFilename) ? null : serverCertificateFilename; Debug.Assert((_encryptionOption & EncryptionOptions.CLIENT_CERT) == 0, "Client certificate authentication support has been removed"); - error = SNINativeMethodWrapper.SNIAddProvider(_physicalStateObj.Handle, Provider.SSL_PROV, authInfo); + error = SniNativeWrapper.SniAddProvider(_physicalStateObj.Handle, Provider.SSL_PROV, authInfo); if (error != TdsEnums.SNI_SUCCESS) { @@ -1239,7 +997,7 @@ private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integ // wait for SSL handshake to complete, so that the SSL context is fully negotiated before we try to use its // Channel Bindings as part of the Windows Authentication context build (SSL handshake must complete // before calling SNISecGenClientContext). - error = SNINativeMethodWrapper.SNIWaitForSSLHandshakeToComplete(_physicalStateObj.Handle, _physicalStateObj.GetTimeoutRemaining(), out uint protocolVersion); + error = SniNativeWrapper.SniWaitForSslHandshakeToComplete(_physicalStateObj.Handle, _physicalStateObj.GetTimeoutRemaining(), out uint protocolVersion); if (error != TdsEnums.SNI_SUCCESS) { @@ -1250,7 +1008,7 @@ private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integ string warningMessage = ((System.Security.Authentication.SslProtocols)protocolVersion).GetProtocolWarning(); if (!string.IsNullOrEmpty(warningMessage)) { - if (!encrypt && LocalAppContextSwitches.SuppressInsecureTLSWarning) + if (!encrypt && LocalAppContextSwitches.SuppressInsecureTlsWarning) { // Skip console warning SqlClientEventSource.Log.TryTraceEvent("{3}", nameof(TdsParser), nameof(EnableSsl), SqlClientLogger.LogLevel.Warning, warningMessage); @@ -1267,7 +1025,6 @@ private void EnableSsl(uint info, SqlConnectionEncryptOption encrypt, bool integ } private PreLoginHandshakeStatus ConsumePreLoginHandshake( - SqlAuthenticationMethod authType, SqlConnectionEncryptOption encrypt, bool trustServerCert, bool integratedSecurity, @@ -1279,7 +1036,6 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( // Assign default values marsCapable = _fMARS; fedAuthRequired = false; - bool is2005OrLater = false; Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); TdsOperationStatus result = _physicalStateObj.TryReadNetworkPacket(); if (result != TdsOperationStatus.Done) @@ -1338,13 +1094,6 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( byte minorVersion = payload[payloadOffset + 1]; int level = (payload[payloadOffset + 2] << 8) | payload[payloadOffset + 3]; - - is2005OrLater = majorVersion >= 9; - if (!is2005OrLater) - { - marsCapable = false; // If pre-2005, MARS not supported. - } - break; case (int)PreLoginOptions.ENCRYPT: @@ -1366,9 +1115,7 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( NOT_SUP, REQ, LOGIN, - OPTIONS_MASK = 0x3f, - CTAIP = 0x40, - CLIENT_CERT = 0x80, + OPTIONS_MASK = 0x3f } */ // Any response other than NOT_SUP means the server supports encryption. @@ -1505,7 +1252,7 @@ private PreLoginHandshakeStatus ConsumePreLoginHandshake( bool shouldValidateServerCert = (_encryptionOption == EncryptionOptions.ON && !trustServerCert) || ((_connHandler._accessTokenInBytes != null || _connHandler._accessTokenCallback != null) && !trustServerCert); uint info = (shouldValidateServerCert ? TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE : 0) - | (is2005OrLater ? TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE : 0); + | TdsEnums.SNI_SSL_USE_SCHANNEL_CACHE; EnableSsl(info, encrypt, integratedSecurity, serverCertificateFilename); } @@ -1792,14 +1539,14 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) // There is an exception here for MARS as its possible that another thread has closed the connection just as we see an error Debug.Assert(SniContext.Undefined != stateObj.DebugOnlyCopyOfSniContext || ((_fMARS) && ((_state == TdsParserState.Closed) || (_state == TdsParserState.Broken))), "SniContext must not be None"); #endif - SniError sniError = new SniError(); - SNINativeMethodWrapper.SNIGetLastError(out sniError); + SniError details = new SniError(); + SniNativeWrapper.SniGetLastError(out details); - if (sniError.sniError != 0) + if (details.sniError != 0) { // handle special SNI error codes that are converted into exception which is not a SqlException. - switch (sniError.sniError) + switch (details.sniError) { case SniErrors.MultiSubnetFailoverWithMoreThan64IPs: // Connecting with the MultiSubnetFailover connection option to a SQL Server instance configured with more than 64 IP addresses is not supported. @@ -1819,7 +1566,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) // error.errorMessage is null terminated with garbage beyond that, since fixed length string errorMessage; - errorMessage = string.IsNullOrEmpty(sniError.errorMessage) ? string.Empty : sniError.errorMessage; + errorMessage = string.IsNullOrEmpty(details.errorMessage) ? string.Empty : details.errorMessage; /* Format SNI errors and add Context Information * * General syntax is: @@ -1833,16 +1580,15 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) * !=null | == 0 | replace text left of errorMessage */ - Debug.Assert(!ADP.IsEmpty(errorMessage), "Empty error message received from SNI"); + Debug.Assert(!string.IsNullOrEmpty(errorMessage), "Empty error message received from SNI"); string sqlContextInfo = StringsHelper.GetString(Enum.GetName(typeof(SniContext), stateObj.SniContext)); - - string providerRid = String.Format("SNI_PN{0}", (int)sniError.provider); + string providerRid = string.Format("SNI_PN{0}", (int)details.provider); string providerName = StringsHelper.GetString(providerRid); Debug.Assert(!string.IsNullOrEmpty(providerName), $"invalid providerResourceId '{providerRid}'"); - uint win32ErrorCode = sniError.nativeError; + int win32ErrorCode = details.nativeError; - if (sniError.sniError == 0) + if (details.sniError == 0) { // Provider error. The message from provider is preceeded with non-localizable info from SNI // strip provider info from SNI @@ -1855,7 +1601,7 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) if (0 <= iColon) { int len = errorMessage.Length; - len -= 2; // exclude "\r\n" sequence + len -= 2; // exclude newline sequence iColon += 2; // skip over ": " sequence len -= iColon; /* @@ -1872,22 +1618,21 @@ internal SqlError ProcessSNIError(TdsParserStateObject stateObj) } else { - // SNI error. Replace the entire message - // - errorMessage = SQL.GetSNIErrorMessage(sniError.sniError); + // SNI error. Replace the entire message. + errorMessage = SQL.GetSNIErrorMessage(details.sniError); // If its a LocalDB error, then nativeError actually contains a LocalDB-specific error code, not a win32 error code - if (sniError.sniError == SniErrors.LocalDBErrorCode) + if (details.sniError == SniErrors.LocalDBErrorCode) { - errorMessage += LocalDBAPI.GetLocalDBMessage((int)sniError.nativeError); + errorMessage += LocalDbApi.GetLocalDbMessage(details.nativeError); win32ErrorCode = 0; } } errorMessage = string.Format("{0} (provider: {1}, error: {2} - {3})", - sqlContextInfo, providerName, (int)sniError.sniError, errorMessage); + sqlContextInfo, providerName, (int)details.sniError, errorMessage); - return new SqlError((int)sniError.nativeError, 0x00, TdsEnums.FATAL_ERROR_CLASS, - _server, errorMessage, sniError.function, (int)sniError.lineNumber, win32ErrorCode); + return new SqlError(details.nativeError, 0x00, TdsEnums.FATAL_ERROR_CLASS, + _server, errorMessage, details.function, (int)details.lineNumber, win32ErrorCode); } internal void CheckResetConnection(TdsParserStateObject stateObj) @@ -1931,17 +1676,16 @@ internal void CheckResetConnection(TdsParserStateObject stateObj) // Check again to see if we need to send reset. Debug.Assert(!stateObj._fResetConnectionSent, "Unexpected state for sending reset connection"); - Debug.Assert(_is2000, "TdsParser.cs: Error! _fResetConnection true when not going against 2000 or later!"); if (_fPreserveTransaction) { // if we are reseting, set bit in header by or'ing with other value - stateObj._outBuff[1] = (Byte)(stateObj._outBuff[1] | TdsEnums.ST_RESET_CONNECTION_PRESERVE_TRANSACTION); + stateObj._outBuff[1] = (byte)(stateObj._outBuff[1] | TdsEnums.ST_RESET_CONNECTION_PRESERVE_TRANSACTION); } else { // if we are reseting, set bit in header by or'ing with other value - stateObj._outBuff[1] = (Byte)(stateObj._outBuff[1] | TdsEnums.ST_RESET_CONNECTION); + stateObj._outBuff[1] = (byte)(stateObj._outBuff[1] | TdsEnums.ST_RESET_CONNECTION); } if (!_fMARS) @@ -2013,8 +1757,6 @@ internal byte[] SerializeShort(int v, TdsParserStateObject stateObj) // internal void WriteShort(int v, TdsParserStateObject stateObj) { - ReliabilitySection.Assert("unreliable call to WriteShort"); // you need to setup for a thread abort somewhere before you call this method - if ((stateObj._outBytesUsed + 2) > stateObj._outBuff.Length) { // if all of the short doesn't fit into the buffer @@ -2055,11 +1797,11 @@ internal byte[] SerializeInt(int v, TdsParserStateObject stateObj) { if (stateObj._bIntBytes == null) { - stateObj._bIntBytes = new byte[4]; + stateObj._bIntBytes = new byte[sizeof(int)]; } else { - Debug.Assert(4 == stateObj._bIntBytes.Length); + Debug.Assert(sizeof(int) == stateObj._bIntBytes.Length); } int current = 0; @@ -2073,8 +1815,6 @@ internal byte[] SerializeInt(int v, TdsParserStateObject stateObj) internal void WriteInt(int v, TdsParserStateObject stateObj) { - ReliabilitySection.Assert("unreliable call to WriteInt"); // you need to setup for a thread abort somewhere before you call this method - if ((stateObj._outBytesUsed + 4) > stateObj._outBuff.Length) { // if all of the int doesn't fit into the buffer @@ -2143,8 +1883,6 @@ internal byte[] SerializeLong(long v, TdsParserStateObject stateObj) internal void WriteLong(long v, TdsParserStateObject stateObj) { - ReliabilitySection.Assert("unreliable call to WriteLong"); // you need to setup for a thread abort somewhere before you call this method - if ((stateObj._outBytesUsed + 8) > stateObj._outBuff.Length) { // if all of the long doesn't fit into the buffer @@ -2190,7 +1928,6 @@ internal byte[] SerializePartialLong(long v, int length) internal void WritePartialLong(long v, int length, TdsParserStateObject stateObj) { - ReliabilitySection.Assert("unreliable call to WritePartialLong"); // you need to setup for a thread abort somewhere before you call this method Debug.Assert(length <= 8, "Length specified is longer than the size of a long"); Debug.Assert(length >= 0, "Length should not be negative"); @@ -2226,7 +1963,7 @@ internal void WriteUnsignedLong(ulong uv, TdsParserStateObject stateObj) // internal byte[] SerializeDouble(double v) { - if (Double.IsInfinity(v) || Double.IsNaN(v)) + if (double.IsInfinity(v) || double.IsNaN(v)) { throw ADP.ParameterValueOutOfRange(v.ToString()); } @@ -2248,44 +1985,6 @@ internal void PrepareResetConnection(bool preserveTransaction) _fPreserveTransaction = preserveTransaction; } - internal bool RunReliably(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) - { - RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#endif //DEBUG - return Run(runBehavior, cmdHandler, dataStream, bulkCopyHandler, stateObj); -#if DEBUG - } - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - catch (OutOfMemoryException) - { - _connHandler.DoomThisConnection(); - throw; - } - catch (StackOverflowException) - { - _connHandler.DoomThisConnection(); - throw; - } - catch (ThreadAbortException) - { - _connHandler.DoomThisConnection(); - throw; - } - } - internal bool Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) { bool syncOverAsync = stateObj._syncOverAsync; @@ -2346,7 +2045,6 @@ internal static bool IsValidTdsToken(byte token) // Main parse loop for the top-level tds tokens, calls back into the I*Handler interfaces internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, out bool dataReady) { - ReliabilitySection.Assert("unreliable call to Run"); // you need to setup for a thread abort somewhere before you call this method Debug.Assert((SniContext.Undefined != stateObj.SniContext) && // SniContext must not be Undefined ((stateObj._attentionSent) || ((SniContext.Snix_Execute != stateObj.SniContext) && (SniContext.Snix_SendRows != stateObj.SniContext))), // SniContext should not be Execute or SendRows unless attention was sent (and, therefore, we are looking for an ACK) $"Unexpected SniContext on call to TryRun; SniContext={stateObj.SniContext}"); @@ -2738,7 +2436,7 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle { _SqlMetaDataSet metadata; result = TryProcessMetaData(tokenLength, stateObj, out metadata, - cmdHandler != null ? cmdHandler.ColumnEncryptionSetting : SqlCommandColumnEncryptionSetting.UseConnectionSetting); + cmdHandler?.ColumnEncryptionSetting ?? SqlCommandColumnEncryptionSetting.UseConnectionSetting); if (result != TdsOperationStatus.Done) { return result; @@ -2873,7 +2571,7 @@ internal TdsOperationStatus TryRun(RunBehavior runBehavior, SqlCommand cmdHandle { SqlReturnValue returnValue; result = TryProcessReturnValue(tokenLength, stateObj, out returnValue, - cmdHandler != null ? cmdHandler.ColumnEncryptionSetting : SqlCommandColumnEncryptionSetting.UseConnectionSetting); + cmdHandler?.ColumnEncryptionSetting ?? SqlCommandColumnEncryptionSetting.UseConnectionSetting); if (result != TdsOperationStatus.Done) { return result; @@ -3104,31 +2802,7 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb } break; - case TdsEnums.ENV_CHARSET: - // we copied this behavior directly from luxor - see charset envchange - // section from sqlctokn.c - Debug.Assert(!_is2000, "Received ENV_CHARSET on non 7.0 server!"); - result = TryReadTwoStringFields(env, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (env._newValue == TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_STRING) - { - _defaultCodePage = TdsEnums.DEFAULT_ENGLISH_CODE_PAGE_VALUE; - _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); - } - else - { - Debug.Assert(env._newValue.Length > TdsEnums.CHARSET_CODE_PAGE_OFFSET, "TdsParser.ProcessEnvChange(): charset value received with length <=10"); - - string stringCodePage = env._newValue.Substring(TdsEnums.CHARSET_CODE_PAGE_OFFSET); - - _defaultCodePage = int.Parse(stringCodePage, NumberStyles.Integer, CultureInfo.InvariantCulture); - _defaultEncoding = System.Text.Encoding.GetEncoding(_defaultCodePage); - } - - break; + // TdsEnums.ENV_CHARSET (3) is only supported in TDS <= 7 which is no longer supported case TdsEnums.ENV_PACKETSIZE: // take care of packet size right here @@ -3154,31 +2828,16 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb // Update SNI ConsumerInfo value to be resulting packet size uint unsignedPacketSize = (uint)packetSize; - uint bufferSizeResult = SNINativeMethodWrapper.SNISetInfo(_physicalStateObj.Handle, QueryType.SNI_QUERY_CONN_BUFSIZE, ref unsignedPacketSize); + uint bufferSizeResult = _physicalStateObj.SetConnectionBufferSize(ref unsignedPacketSize); Debug.Assert(bufferSizeResult == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SNISetInfo"); } break; - case TdsEnums.ENV_LOCALEID: - // UNDONE: this LCID may be incorrect for OEM code pages on 7.0 - // need a way to get lcid from code page - result = TryReadTwoStringFields(env, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - _defaultLCID = int.Parse(env._newValue, NumberStyles.Integer, CultureInfo.InvariantCulture); - break; + // TdsEnums.ENV_LOCALE (5) is only supported in TDS <= 7 which is no longer supported - case TdsEnums.ENV_COMPFLAGS: - result = TryReadTwoStringFields(env, stateObj); - if (result != TdsOperationStatus.Done) - { - return result; - } - break; + // TdsEnums.ENV_COMPFLAGS (6) is only supported in TDS <= 7 which is no longer supported case TdsEnums.ENV_COLLATION: Debug.Assert(env._newLength == 5 || env._newLength == 0, "Improper length in new collation!"); @@ -3200,11 +2859,10 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb _defaultCollation = env._newCollation; _defaultLCID = env._newCollation.LCID; - // UTF8 collation if (env._newCollation.IsUTF8) { - _defaultEncoding = Encoding.UTF8; + _defaultEncoding = s_utf8EncodingWithoutBom; } else { @@ -3242,8 +2900,6 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb case TdsEnums.ENV_ENLISTDTC: case TdsEnums.ENV_DEFECTDTC: case TdsEnums.ENV_TRANSACTIONENDED: - Debug.Assert(_is2005, "Received new ENVCHANGE transaction/DTC token on pre 9.0 server!"); - result = stateObj.TryReadByte(out byteLength); if (result != TdsOperationStatus.Done) { @@ -3303,7 +2959,6 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb break; case TdsEnums.ENV_PROMOTETRANSACTION: - Debug.Assert(_is2005, "Received new ENVCHANGE tokens on pre 9.0 server!"); result = stateObj.TryReadInt32(out env._newLength); if (result != TdsOperationStatus.Done) { @@ -3332,8 +2987,6 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb case TdsEnums.ENV_TRANSACTIONMANAGERADDRESS: case TdsEnums.ENV_SPRESETCONNECTIONACK: - // TODO UNDONE BUGBUG - need to implement support for these env changes - Debug.Assert(_is2005, "Received new ENVCHANGE tokens on pre 9.0 server!"); result = TryReadTwoBinaryFields(env, stateObj); if (result != TdsOperationStatus.Done) { @@ -3342,7 +2995,6 @@ private TdsOperationStatus TryProcessEnvChange(int tokenLength, TdsParserStateOb break; case TdsEnums.ENV_USERINSTANCE: - Debug.Assert(!_is2005, "Received ENV_USERINSTANCE on non 9.0 server!"); result = TryReadTwoStringFields(env, stateObj); if (result != TdsOperationStatus.Done) { @@ -3507,50 +3159,13 @@ private TdsOperationStatus TryProcessDone(SqlCommand cmd, SqlDataReader reader, return result; } - if (_is2005) - { - long longCount; - result = stateObj.TryReadInt64(out longCount); - if (result != TdsOperationStatus.Done) - { - return result; - } - count = (int)longCount; - } - else + long longCount; + result = stateObj.TryReadInt64(out longCount); + if (result != TdsOperationStatus.Done) { - result = stateObj.TryReadInt32(out count); - if (result != TdsOperationStatus.Done) - { - return result; - } - - // If we haven't yet completed processing login token stream yet, we may be talking to a 2005 server - // In that case we still have to read another 4 bytes - // But don't try to read beyond the TDS stream in this case, because it generates errors if login failed. - if (_state == TdsParserState.OpenNotLoggedIn) - { - // Login incomplete, if we are reading from 2005 we need to read another int - if (stateObj._inBytesRead > stateObj._inBytesUsed) - { - byte b; - result = stateObj.TryPeekByte(out b); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (b == 0) - { - // This is an invalid token value - result = stateObj.TryReadInt32(out count); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - } - } + return result; } + count = (int)longCount; // We get a done token with the attention bit set if (TdsEnums.DONE_ATTN == (status & TdsEnums.DONE_ATTN)) @@ -3640,7 +3255,7 @@ private TdsOperationStatus TryProcessDone(SqlCommand cmd, SqlDataReader reader, Debug.Assert(!((sqlTransaction != null && _distributedTransaction != null) || (_userStartedLocalTransaction != null && _distributedTransaction != null)) , "ProcessDone - have both distributed and local transactions not null!"); - */ + */ // WebData 112722 stateObj.DecrementOpenResultCount(); @@ -4127,8 +3742,8 @@ private TdsOperationStatus TryProcessSessionState(TdsParserStateObject stateObj, if (!recoverable) { checked - { - sdata._unrecoverableStatesCount++; + { + sdata._unrecoverableStatesCount++; } } } @@ -4149,8 +3764,8 @@ private TdsOperationStatus TryProcessSessionState(TdsParserStateObject stateObj, else { checked - { - sdata._unrecoverableStatesCount++; + { + sdata._unrecoverableStatesCount++; } } sv._recoverable = recoverable; @@ -4227,50 +3842,30 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out // information provided by S. Ashwin switch (majorMinor) { - case TdsEnums.SQL70OR2000_MAJOR << 24 | TdsEnums.DEFAULT_MINOR: // 7.0 & 2000 RTM - // note that 7.0 and 2000 can only be distinguished by the increment - switch (increment) - { - case TdsEnums.SQL2000_INCREMENT: - _is2000 = true; - break; - case TdsEnums.SQL70_INCREMENT: - // no flag will be set - break; - default: - throw SQL.InvalidTDSVersion(); - } - break; - case TdsEnums.SQL2000SP1_MAJOR << 24 | TdsEnums.SQL2000SP1_MINOR: // 2000 SP1 - if (increment != TdsEnums.SQL2000SP1_INCREMENT) - { throw SQL.InvalidTDSVersion(); } - _is2000SP1 = true; - break; case TdsEnums.SQL2005_MAJOR << 24 | TdsEnums.SQL2005_RTM_MINOR: // 2005 if (increment != TdsEnums.SQL2005_INCREMENT) - { - throw SQL.InvalidTDSVersion(); + { + throw SQL.InvalidTDSVersion(); } - _is2005 = true; break; case TdsEnums.SQL2008_MAJOR << 24 | TdsEnums.SQL2008_MINOR: if (increment != TdsEnums.SQL2008_INCREMENT) - { - throw SQL.InvalidTDSVersion(); + { + throw SQL.InvalidTDSVersion(); } _is2008 = true; break; case TdsEnums.SQL2012_MAJOR << 24 | TdsEnums.SQL2012_MINOR: if (increment != TdsEnums.SQL2012_INCREMENT) - { - throw SQL.InvalidTDSVersion(); + { + throw SQL.InvalidTDSVersion(); } _is2012 = true; break; - case (uint)TdsEnums.TDS8_MAJOR << 24 | TdsEnums.TDS8_MINOR: + case TdsEnums.TDS8_MAJOR << 24 | TdsEnums.TDS8_MINOR: if (increment != TdsEnums.TDS8_INCREMENT) - { - throw SQL.InvalidTDSVersion(); + { + throw SQL.InvalidTDSVersion(); } _is2022 = true; break; @@ -4279,11 +3874,6 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out } _is2012 |= _is2022; _is2008 |= _is2012; - _is2005 |= _is2008; - _is2000SP1 |= _is2005; // includes all lower versions - _is2000 |= _is2000SP1; // - - a.isVersion8 = _is2000; stateObj._outBytesUsed = stateObj._outputHeaderLen; byte len; @@ -4325,7 +3915,6 @@ private TdsOperationStatus TryProcessLoginAck(TdsParserStateObject stateObj, out Debug.Assert(_state == TdsParserState.OpenNotLoggedIn, "ProcessLoginAck called with state not TdsParserState.OpenNotLoggedIn"); _state = TdsParserState.OpenLoggedIn; - if (_is2005) { if (_fMARS) { @@ -4361,8 +3950,7 @@ private TdsOperationStatus TryProcessFedAuthInfo(TdsParserStateObject stateObj, // read how many FedAuthInfo options there are uint optionsCount; - TdsOperationStatus result = stateObj.TryReadUInt32(out optionsCount); - if (result != TdsOperationStatus.Done) + if (stateObj.TryReadUInt32(out optionsCount) != TdsOperationStatus.Done) { SqlClientEventSource.Log.TryTraceEvent(" Failed to read CountOfInfoIDs in FEDAUTHINFO token stream."); throw SQL.ParsingError(ParsingErrorState.FedAuthInfoFailedToReadCountOfInfoIds); @@ -4403,8 +3991,8 @@ private TdsOperationStatus TryProcessFedAuthInfo(TdsParserStateObject stateObj, uint currentOptionOffset = checked(i * optionSize); byte id = tokenData[currentOptionOffset]; - uint dataLen = BitConverter.ToUInt32(tokenData, checked((int)(currentOptionOffset + 1))); - uint dataOffset = BitConverter.ToUInt32(tokenData, checked((int)(currentOptionOffset + 5))); + uint dataLen = BinaryPrimitives.ReadUInt32LittleEndian(tokenData.AsSpan(checked((int)(currentOptionOffset + 1)))); + uint dataOffset = BinaryPrimitives.ReadUInt32LittleEndian(tokenData.AsSpan(checked((int)(currentOptionOffset + 5)))); if (SqlClientEventSource.Log.IsAdvancedTraceOn()) { SqlClientEventSource.Log.AdvancedTraceEvent(" FedAuthInfoOpt: ID={0}, DataLen={1}, Offset={2}", id, dataLen.ToString(CultureInfo.InvariantCulture), dataOffset.ToString(CultureInfo.InvariantCulture)); @@ -4554,47 +4142,11 @@ internal TdsOperationStatus TryProcessError(byte token, TdsParserStateObject sta } int line; - if (_is2005) - { - result = stateObj.TryReadInt32(out line); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - else - { - ushort shortLine; - result = stateObj.TryReadUInt16(out shortLine); - if (result != TdsOperationStatus.Done) - { - return result; - } - line = shortLine; - // If we haven't yet completed processing login token stream yet, we may be talking to a 2005 server - // In that case we still have to read another 2 bytes - if (_state == TdsParserState.OpenNotLoggedIn) - { - // Login incomplete - byte b; - result = stateObj.TryPeekByte(out b); - if (result != TdsOperationStatus.Done) - { - return result; - } - if (b == 0) - { - // This is an invalid token value - ushort value; - result = stateObj.TryReadUInt16(out value); - if (result != TdsOperationStatus.Done) - { - return result; - } - line = (line << 16) + value; - } - } + result = stateObj.TryReadInt32(out line); + if (result != TdsOperationStatus.Done) + { + return result; } int batchIndex = -1; if (command != null) @@ -4605,23 +4157,19 @@ internal TdsOperationStatus TryProcessError(byte token, TdsParserStateObject sta return TdsOperationStatus.Done; } - internal TdsOperationStatus TryProcessReturnValue(int length, - TdsParserStateObject stateObj, - out SqlReturnValue returnValue, - SqlCommandColumnEncryptionSetting columnEncryptionSetting) + TdsParserStateObject stateObj, + out SqlReturnValue returnValue, + SqlCommandColumnEncryptionSetting columnEncryptionSetting) { TdsOperationStatus result; returnValue = null; SqlReturnValue rec = new SqlReturnValue(); rec.length = length; // In 2005 this length is -1 - if (_is2005) + result = stateObj.TryReadUInt16(out rec.parmIndex); + if (result != TdsOperationStatus.Done) { - result = stateObj.TryReadUInt16(out rec.parmIndex); - if (result != TdsOperationStatus.Done) - { - return result; - } + return result; } byte len; result = stateObj.TryReadByte(out len); @@ -4650,23 +4198,10 @@ internal TdsOperationStatus TryProcessReturnValue(int length, uint userType; // read user type - 4 bytes 2005, 2 backwards - if (Is2005OrNewer) - { - result = stateObj.TryReadUInt32(out userType); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - else + result = stateObj.TryReadUInt32(out userType); + if (result != TdsOperationStatus.Done) { - ushort userTypeShort; - result = stateObj.TryReadUInt16(out userTypeShort); - if (result != TdsOperationStatus.Done) - { - return result; - } - userType = userTypeShort; + return result; } // Read off the flags. @@ -4685,7 +4220,7 @@ internal TdsOperationStatus TryProcessReturnValue(int length, } // Check if the column is encrypted. - if (_serverSupportsColumnEncryption) + if (IsColumnEncryptionSupported) { rec.isEncrypted = (TdsEnums.IsEncrypted == (flags & TdsEnums.IsEncrypted)); } @@ -4726,22 +4261,11 @@ internal TdsOperationStatus TryProcessReturnValue(int length, // always use the nullable type for parameters if 2000 or later // 7.0 sometimes sends fixed length return values - if (_is2000) + rec.tdsType = rec.metaType.NullableType; + rec.IsNullable = true; + if (tdsLen == TdsEnums.SQL_USHORTVARMAXLEN) { - rec.tdsType = rec.metaType.NullableType; - rec.IsNullable = true; - if (tdsLen == TdsEnums.SQL_USHORTVARMAXLEN) - { - Debug.Assert(_is2005, "plp data from pre-2005 server"); - rec.metaType = MetaType.GetMaxMetaTypeFromMetaType(rec.metaType); - } - } - else - { // For 7.0, keep the fixed type if that is what is returned - if (rec.metaType.NullableType == tdsType) - rec.IsNullable = true; - - rec.tdsType = (byte)tdsType; + rec.metaType = MetaType.GetMaxMetaTypeFromMetaType(rec.metaType); } if (rec.type == SqlDbType.Decimal) @@ -4776,6 +4300,15 @@ internal TdsOperationStatus TryProcessReturnValue(int length, } } + if (tdsType == TdsEnums.SQLVECTOR) + { + result = stateObj.TryReadByte(out rec.scale); + if (result != TdsOperationStatus.Done) + { + return result; + } + } + if (rec.type == SqlDbType.Xml) { // Read schema info @@ -4837,7 +4370,7 @@ internal TdsOperationStatus TryProcessReturnValue(int length, } } } - else if (_is2000 && rec.metaType.IsCharType) + else if (rec.metaType.IsCharType && rec.metaType.SqlDbType != SqlDbTypeExtensions.Json) { // read the collation for 8.x servers result = TryProcessCollation(stateObj, out rec.collation); @@ -4848,7 +4381,7 @@ internal TdsOperationStatus TryProcessReturnValue(int length, if (rec.collation.IsUTF8) { // UTF8 collation - rec.encoding = Encoding.UTF8; + rec.encoding = s_utf8EncodingWithoutBom; } else { @@ -4869,7 +4402,7 @@ internal TdsOperationStatus TryProcessReturnValue(int length, } // For encrypted parameters, read the unencrypted type and encryption information. - if (_serverSupportsColumnEncryption && rec.isEncrypted) + if (IsColumnEncryptionSupported && rec.isEncrypted) { result = TryProcessTceCryptoMetadata(stateObj, rec, cipherTable: null, columnEncryptionSetting: columnEncryptionSetting, isReturnValue: true); if (result != TdsOperationStatus.Done) @@ -4888,13 +4421,18 @@ internal TdsOperationStatus TryProcessReturnValue(int length, } // always read as sql types - Debug.Assert(valLen < (ulong)(Int32.MaxValue), "ProcessReturnValue received data size > 2Gb"); + Debug.Assert(valLen < (ulong)(int.MaxValue), "ProcessReturnValue received data size > 2Gb"); - int intlen = valLen > (ulong)(Int32.MaxValue) ? Int32.MaxValue : (int)valLen; + int intlen = valLen > (ulong)(int.MaxValue) ? int.MaxValue : (int)valLen; if (rec.metaType.IsPlp) { - intlen = Int32.MaxValue; // If plp data, read it all + intlen = int.MaxValue; // If plp data, read it all + } + + if (rec.type == SqlDbTypeExtensions.Vector) + { + rec.length = tdsLen; } if (isNull) @@ -4926,7 +4464,7 @@ internal TdsOperationStatus TryProcessTceCryptoMetadata(TdsParserStateObject sta // Read the ordinal into cipher table ushort index = 0; - UInt32 userType; + uint userType; TdsOperationStatus result; // For return values there is not cipher table and no ordinal. @@ -5066,7 +4604,7 @@ private void WriteCollation(SqlCollation collation, TdsParserStateObject stateOb } else { - _physicalStateObj.WriteByte(sizeof(UInt32) + sizeof(byte)); + _physicalStateObj.WriteByte(sizeof(uint) + sizeof(byte)); WriteUnsignedInt(collation._info, _physicalStateObj); _physicalStateObj.WriteByte(collation._sortId); } @@ -5176,101 +4714,84 @@ internal void DrainData(TdsParserStateObject stateObj) RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); try { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - try + SqlDataReader.SharedState sharedState = stateObj._readerState; + if (sharedState != null && sharedState._dataReady) { - SqlDataReader.SharedState sharedState = stateObj._readerState; - if (sharedState != null && sharedState._dataReady) + var metadata = stateObj._cleanupMetaData; + if (stateObj._partialHeaderBytesRead > 0) { - var metadata = stateObj._cleanupMetaData; - if (stateObj._partialHeaderBytesRead > 0) + TdsOperationStatus result = stateObj.TryProcessHeader(); + if (result != TdsOperationStatus.Done) { - TdsOperationStatus result = stateObj.TryProcessHeader(); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } + throw SQL.SynchronousCallMayNotPend(); } - if (0 == sharedState._nextColumnHeaderToRead) + } + if (0 == sharedState._nextColumnHeaderToRead) + { + // i. user called read but didn't fetch anything + TdsOperationStatus result = stateObj.Parser.TrySkipRow(stateObj._cleanupMetaData, stateObj); + if (result != TdsOperationStatus.Done) { - // i. user called read but didn't fetch anything - TdsOperationStatus result = stateObj.Parser.TrySkipRow(stateObj._cleanupMetaData, stateObj); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } + throw SQL.SynchronousCallMayNotPend(); } - else + } + else + { + // iia. if we still have bytes left from a partially read column, skip + if (sharedState._nextColumnDataToRead < sharedState._nextColumnHeaderToRead) { - // iia. if we still have bytes left from a partially read column, skip - if (sharedState._nextColumnDataToRead < sharedState._nextColumnHeaderToRead) + if ((sharedState._nextColumnHeaderToRead > 0) && (metadata[sharedState._nextColumnHeaderToRead - 1].metaType.IsPlp)) { - if ((sharedState._nextColumnHeaderToRead > 0) && (metadata[sharedState._nextColumnHeaderToRead - 1].metaType.IsPlp)) - { - if (stateObj._longlen != 0) - { - TdsOperationStatus result = TrySkipPlpValue(UInt64.MaxValue, stateObj, out _); - if (result != TdsOperationStatus.Done) - { - throw SQL.SynchronousCallMayNotPend(); - } - } - } - - else if (0 < sharedState._columnDataBytesRemaining) + if (stateObj._longlen != 0) { - TdsOperationStatus result = stateObj.TrySkipLongBytes(sharedState._columnDataBytesRemaining); + TdsOperationStatus result = TrySkipPlpValue(UInt64.MaxValue, stateObj, out _); if (result != TdsOperationStatus.Done) { throw SQL.SynchronousCallMayNotPend(); } } - } - - // Read the remaining values off the wire for this row - if (stateObj.Parser.TrySkipRow(metadata, sharedState._nextColumnHeaderToRead, stateObj) != TdsOperationStatus.Done) + else if (0 < sharedState._columnDataBytesRemaining) { - throw SQL.SynchronousCallMayNotPend(); + TdsOperationStatus result = stateObj.TrySkipLongBytes(sharedState._columnDataBytesRemaining); + if (result != TdsOperationStatus.Done) + { + throw SQL.SynchronousCallMayNotPend(); + } } + + } + + + // Read the remaining values off the wire for this row + if (stateObj.Parser.TrySkipRow(metadata, sharedState._nextColumnHeaderToRead, stateObj) != TdsOperationStatus.Done) + { + throw SQL.SynchronousCallMayNotPend(); } } - Run(RunBehavior.Clean, null, null, null, stateObj); - } - catch - { - _connHandler.DoomThisConnection(); - throw; } + Run(RunBehavior.Clean, null, null, null, stateObj); } -#if DEBUG - finally + catch { - tdsReliabilitySection.Stop(); + _connHandler.DoomThisConnection(); + throw; } -#endif //DEBUG } - catch (System.OutOfMemoryException) + catch (OutOfMemoryException) { _connHandler.DoomThisConnection(); throw; } - catch (System.StackOverflowException) + catch (StackOverflowException) { _connHandler.DoomThisConnection(); throw; } - catch (System.Threading.ThreadAbortException) + catch (ThreadAbortException) { _connHandler.DoomThisConnection(); throw; @@ -5347,7 +4868,7 @@ internal TdsOperationStatus TryProcessAltMetaData(int cColumns, TdsParserStateOb return result; } - if (ADP.IsEmpty(col.column)) + if (string.IsNullOrEmpty(col.column)) { // create column name from op switch (col.op) @@ -5588,7 +5109,7 @@ internal TdsOperationStatus TryProcessMetaData(int cColumns, TdsParserStateObjec // Read the cipher info table first SqlTceCipherInfoTable cipherTable = null; - if (_serverSupportsColumnEncryption) + if (IsColumnEncryptionSupported) { TdsOperationStatus result = TryProcessCipherInfoTable(stateObj, out cipherTable); if (result != TdsOperationStatus.Done) @@ -5615,10 +5136,7 @@ internal TdsOperationStatus TryProcessMetaData(int cColumns, TdsParserStateObjec return TdsOperationStatus.Done; } - private bool IsVarTimeTds(byte tdsType) - { - return tdsType == TdsEnums.SQLTIME || tdsType == TdsEnums.SQLDATETIME2 || tdsType == TdsEnums.SQLDATETIMEOFFSET; - } + private bool IsVarTimeTds(byte tdsType) => tdsType == TdsEnums.SQLTIME || tdsType == TdsEnums.SQLDATETIME2 || tdsType == TdsEnums.SQLDATETIMEOFFSET; private TdsOperationStatus TryProcessTypeInfo(TdsParserStateObject stateObj, SqlMetaDataPriv col, UInt32 userType) { @@ -5649,92 +5167,84 @@ private TdsOperationStatus TryProcessTypeInfo(TdsParserStateObject stateObj, Sql col.metaType = MetaType.GetSqlDataType(tdsType, userType, col.length); col.type = col.metaType.SqlDbType; + col.tdsType = (col.IsNullable ? col.metaType.NullableType : col.metaType.TDSType); - // If 7.0, do not change to nullable type - if (_is2000) - col.tdsType = (col.IsNullable ? col.metaType.NullableType : col.metaType.TDSType); - else - col.tdsType = tdsType; + if (TdsEnums.SQLUDT == tdsType) + { + result = TryProcessUDTMetaData(col, stateObj); + if (result != TdsOperationStatus.Done) + { + return result; + } + } - if (_is2005) + if (col.length == TdsEnums.SQL_USHORTVARMAXLEN) { - if (TdsEnums.SQLUDT == tdsType) + Debug.Assert(tdsType == TdsEnums.SQLXMLTYPE || + tdsType == TdsEnums.SQLBIGVARCHAR || + tdsType == TdsEnums.SQLBIGVARBINARY || + tdsType == TdsEnums.SQLNVARCHAR || + tdsType == TdsEnums.SQLUDT, + "Invalid streaming datatype"); + col.metaType = MetaType.GetMaxMetaTypeFromMetaType(col.metaType); + Debug.Assert(col.metaType.IsLong, "Max datatype not IsLong"); + col.length = int.MaxValue; + if (tdsType == TdsEnums.SQLXMLTYPE) { - result = TryProcessUDTMetaData((SqlMetaDataPriv)col, stateObj); + byte schemapresent; + result = stateObj.TryReadByte(out schemapresent); if (result != TdsOperationStatus.Done) { return result; } - } - if (col.length == TdsEnums.SQL_USHORTVARMAXLEN) - { - Debug.Assert(tdsType == TdsEnums.SQLXMLTYPE || - tdsType == TdsEnums.SQLBIGVARCHAR || - tdsType == TdsEnums.SQLBIGVARBINARY || - tdsType == TdsEnums.SQLNVARCHAR || - tdsType == TdsEnums.SQLUDT, - "Invalid streaming datatype"); - col.metaType = MetaType.GetMaxMetaTypeFromMetaType(col.metaType); - Debug.Assert(col.metaType.IsLong, "Max datatype not IsLong"); - col.length = Int32.MaxValue; - if (tdsType == TdsEnums.SQLXMLTYPE) + if ((schemapresent & 1) != 0) { - byte schemapresent; - result = stateObj.TryReadByte(out schemapresent); + result = stateObj.TryReadByte(out byteLen); if (result != TdsOperationStatus.Done) { return result; } - - if ((schemapresent & 1) != 0) + if (col.xmlSchemaCollection is null) + { + col.xmlSchemaCollection = new SqlMetaDataXmlSchemaCollection(); + } + if (byteLen != 0) { - result = stateObj.TryReadByte(out byteLen); + result = stateObj.TryReadString(byteLen, out col.xmlSchemaCollection.Database); if (result != TdsOperationStatus.Done) { return result; } - if (col.xmlSchemaCollection is null) - { - col.xmlSchemaCollection = new SqlMetaDataXmlSchemaCollection(); - } - if (byteLen != 0) - { - result = stateObj.TryReadString(byteLen, out col.xmlSchemaCollection.Database); - if (result != TdsOperationStatus.Done) - { - return result; - } - } + } - result = stateObj.TryReadByte(out byteLen); + result = stateObj.TryReadByte(out byteLen); + if (result != TdsOperationStatus.Done) + { + return result; + } + if (byteLen != 0) + { + result = stateObj.TryReadString(byteLen, out col.xmlSchemaCollection.OwningSchema); if (result != TdsOperationStatus.Done) { return result; } - if (byteLen != 0) - { - result = stateObj.TryReadString(byteLen, out col.xmlSchemaCollection.OwningSchema); - if (result != TdsOperationStatus.Done) - { - return result; - } - } + } - short shortLen; - result = stateObj.TryReadInt16(out shortLen); + short shortLen; + result = stateObj.TryReadInt16(out shortLen); + if (result != TdsOperationStatus.Done) + { + return result; + } + if (byteLen != 0) + { + result = stateObj.TryReadString(shortLen, out col.xmlSchemaCollection.Name); if (result != TdsOperationStatus.Done) { return result; } - if (byteLen != 0) - { - result = stateObj.TryReadString(shortLen, out col.xmlSchemaCollection.Name); - if (result != TdsOperationStatus.Done) - { - return result; - } - } } } } @@ -5787,7 +5297,7 @@ private TdsOperationStatus TryProcessTypeInfo(TdsParserStateObject stateObj, Sql } // read the collation for 7.x servers - if (_is2000 && col.metaType.IsCharType && (tdsType != TdsEnums.SQLXMLTYPE) && (tdsType != TdsEnums.SQLJSON)) + if (col.metaType.IsCharType && (tdsType != TdsEnums.SQLXMLTYPE) && (tdsType != TdsEnums.SQLJSON)) { result = TryProcessCollation(stateObj, out col.collation); if (result != TdsOperationStatus.Done) @@ -5797,7 +5307,7 @@ private TdsOperationStatus TryProcessTypeInfo(TdsParserStateObject stateObj, Sql if (col.collation.IsUTF8) { // UTF8 collation - col.encoding = Encoding.UTF8; + col.encoding = s_utf8EncodingWithoutBom; } else { @@ -5816,6 +5326,15 @@ private TdsOperationStatus TryProcessTypeInfo(TdsParserStateObject stateObj, Sql } } + if (col.type == SqlDbTypeExtensions.Vector) + { + result = stateObj.TryReadByte(out col.scale); + if (result != TdsOperationStatus.Done) + { + return result; + } + } + return TdsOperationStatus.Done; } @@ -5826,25 +5345,12 @@ private TdsOperationStatus TryCommonProcessMetaData(TdsParserStateObject stateOb TdsOperationStatus result; // read user type - 4 bytes 2005, 2 backwards - if (Is2005OrNewer) - { - result = stateObj.TryReadUInt32(out userType); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - else + result = stateObj.TryReadUInt32(out userType); + if (result != TdsOperationStatus.Done) { - ushort userTypeShort; - result = stateObj.TryReadUInt16(out userTypeShort); - if (result != TdsOperationStatus.Done) - { - return result; - } - userType = userTypeShort; + return result; } - + // read flags and set appropriate flags in structure byte flags; result = stateObj.TryReadByte(out flags); @@ -5865,7 +5371,8 @@ private TdsOperationStatus TryCommonProcessMetaData(TdsParserStateObject stateOb } col.IsColumnSet = (TdsEnums.IsColumnSet == (flags & TdsEnums.IsColumnSet)); - if (fColMD && _serverSupportsColumnEncryption) + + if (fColMD && IsColumnEncryptionSupported) { col.isEncrypted = (TdsEnums.IsEncrypted == (flags & TdsEnums.IsEncrypted)); } @@ -5880,39 +5387,16 @@ private TdsOperationStatus TryCommonProcessMetaData(TdsParserStateObject stateOb // Read tablename if present if (col.metaType.IsLong && !col.metaType.IsPlp) { - if (_is2005) - { - int unusedLen = 0xFFFF; //We ignore this value - result = TryProcessOneTable(stateObj, ref unusedLen, out col.multiPartTableName); - if (result != TdsOperationStatus.Done) - { - return result; - } - } - else + int unusedLen = 0xFFFF; //We ignore this value + result = TryProcessOneTable(stateObj, ref unusedLen, out col.multiPartTableName); + if (result != TdsOperationStatus.Done) { - ushort shortLen; - result = stateObj.TryReadUInt16(out shortLen); - if (result != TdsOperationStatus.Done) - { - return result; - } - string tableName; - result = stateObj.TryReadString(shortLen, out tableName); - if (result != TdsOperationStatus.Done) - { - return result; - } - // with Sql2000 this is returned as an unquoted mix of catalog.owner.table - // all of which may contain "." and unable to parse correctly from the string alone - // example "select * from pubs..[A.B.C.D.E]" AND only when * will contain a image/text/ntext column - // by delay parsing from execute to SqlDataReader.GetSchemaTable to enable more scenarios - col.multiPartTableName = new MultiPartTableName(tableName); + return result; } } // Read the TCE column cryptoinfo - if (fColMD && _serverSupportsColumnEncryption && col.isEncrypted) + if (fColMD && IsColumnEncryptionSupported && col.isEncrypted) { // If the column is encrypted, we should have a valid cipherTable if (cipherTable != null) @@ -6023,7 +5507,7 @@ private TdsOperationStatus TryProcessUDTMetaData(SqlMetaDataPriv metaData, TdsPa } private void WriteUDTMetaData(object value, string database, string schema, string type, - TdsParserStateObject stateObj) + TdsParserStateObject stateObj) { // database if (string.IsNullOrEmpty(database)) @@ -6103,93 +5587,70 @@ private TdsOperationStatus TryProcessOneTable(TdsParserStateObject stateObj, ref multiPartTableName = default(MultiPartTableName); - if (_is2000SP1) - { - - mpt = new MultiPartTableName(); - byte nParts; + mpt = new MultiPartTableName(); + byte nParts; - // Find out how many parts in the TDS stream - TdsOperationStatus result = stateObj.TryReadByte(out nParts); + // Find out how many parts in the TDS stream + TdsOperationStatus result = stateObj.TryReadByte(out nParts); + if (result != TdsOperationStatus.Done) + { + return result; + } + length--; + if (nParts == 4) + { + result = stateObj.TryReadUInt16(out tableLen); if (result != TdsOperationStatus.Done) { return result; } - length--; - if (nParts == 4) + length -= 2; + result = stateObj.TryReadString(tableLen, out value); + if (result != TdsOperationStatus.Done) { - result = stateObj.TryReadUInt16(out tableLen); - if (result != TdsOperationStatus.Done) - { - return result; - } - length -= 2; - result = stateObj.TryReadString(tableLen, out value); - if (result != TdsOperationStatus.Done) - { - return result; - } - mpt.ServerName = value; - nParts--; - length -= (tableLen * 2); // wide bytes + return result; } - if (nParts == 3) + mpt.ServerName = value; + nParts--; + length -= (tableLen * 2); // wide bytes + } + if (nParts == 3) + { + result = stateObj.TryReadUInt16(out tableLen); + if (result != TdsOperationStatus.Done) { - result = stateObj.TryReadUInt16(out tableLen); - if (result != TdsOperationStatus.Done) - { - return result; - } - length -= 2; - result = stateObj.TryReadString(tableLen, out value); - if (result != TdsOperationStatus.Done) - { - return result; - } - mpt.CatalogName = value; - length -= (tableLen * 2); // wide bytes - nParts--; + return result; } - if (nParts == 2) + length -= 2; + result = stateObj.TryReadString(tableLen, out value); + if (result != TdsOperationStatus.Done) { - result = stateObj.TryReadUInt16(out tableLen); - if (result != TdsOperationStatus.Done) - { - return result; - } - length -= 2; - result = stateObj.TryReadString(tableLen, out value); - if (result != TdsOperationStatus.Done) - { - return result; - } - mpt.SchemaName = value; - length -= (tableLen * 2); // wide bytes - nParts--; + return result; } - if (nParts == 1) + mpt.CatalogName = value; + length -= (tableLen * 2); // wide bytes + nParts--; + } + if (nParts == 2) + { + result = stateObj.TryReadUInt16(out tableLen); + if (result != TdsOperationStatus.Done) { - result = stateObj.TryReadUInt16(out tableLen); - if (result != TdsOperationStatus.Done) - { - return result; - } - length -= 2; - result = stateObj.TryReadString(tableLen, out value); - if (result != TdsOperationStatus.Done) - { - return result; - } - mpt.TableName = value; - length -= (tableLen * 2); // wide bytes - nParts--; + return result; } - Debug.Assert(nParts == 0, "ProcessTableName:Unidentified parts in the table name token stream!"); - + length -= 2; + result = stateObj.TryReadString(tableLen, out value); + if (result != TdsOperationStatus.Done) + { + return result; + } + mpt.SchemaName = value; + length -= (tableLen * 2); // wide bytes + nParts--; } - else + if (nParts == 1) { - TdsOperationStatus result = stateObj.TryReadUInt16(out tableLen); + result = stateObj.TryReadUInt16(out tableLen); if (result != TdsOperationStatus.Done) { return result; @@ -6200,10 +5661,11 @@ private TdsOperationStatus TryProcessOneTable(TdsParserStateObject stateObj, ref { return result; } - string tableName = value; + mpt.TableName = value; length -= (tableLen * 2); // wide bytes - mpt = new MultiPartTableName(MultipartIdentifier.ParseMultipartIdentifier(tableName, "[\"", "]\"", Strings.SQL_TDSParserTableName, false)); + nParts--; } + Debug.Assert(nParts == 0, "ProcessTableName:Unidentified parts in the table name token stream!"); multiPartTableName = mpt; return TdsOperationStatus.Done; @@ -6220,12 +5682,13 @@ private TdsOperationStatus TryProcessColInfo(_SqlMetaDataSet columns, SqlDataRea for (int i = 0; i < columns.Length; i++) { _SqlMetaData col = columns[i]; - + result = stateObj.TryReadByte(out _); if (result != TdsOperationStatus.Done) { return result; } + result = stateObj.TryReadByte(out col.tableNum); if (result != TdsOperationStatus.Done) { @@ -6426,9 +5889,9 @@ private TdsOperationStatus TryProcessRow(_SqlMetaDataSet columns, object[] buffe // We only read up to 2Gb. Throw if data is larger. Very large data // should be read in chunks in sequential read mode // For Plp columns, we may have gotten only the length of the first chunk - result = TryReadSqlValue(data, md, md.metaType.IsPlp ? (Int32.MaxValue) : (int)len, stateObj, - SqlCommandColumnEncryptionSetting.Disabled /*Column Encryption Disabled for Bulk Copy*/, - md.column); + result = TryReadSqlValue(data, md, md.metaType.IsPlp ? (int.MaxValue) : (int)len, stateObj, + SqlCommandColumnEncryptionSetting.Disabled /*Column Encryption Disabled for Bulk Copy*/, + md.column); if (result != TdsOperationStatus.Done) { return result; @@ -6449,10 +5912,8 @@ private TdsOperationStatus TryProcessRow(_SqlMetaDataSet columns, object[] buffe /// Determines if a column value should be transparently decrypted (based on SqlCommand and Connection String settings). /// /// true if the value should be transparently decrypted, false otherwise - internal static bool ShouldHonorTceForRead(SqlCommandColumnEncryptionSetting columnEncryptionSetting, - SqlInternalConnectionTds connection) + internal static bool ShouldHonorTceForRead(SqlCommandColumnEncryptionSetting columnEncryptionSetting, SqlInternalConnectionTds connection) { - // Command leve setting trumps all switch (columnEncryptionSetting) { @@ -6466,16 +5927,15 @@ internal static bool ShouldHonorTceForRead(SqlCommandColumnEncryptionSetting col // Check connection level setting! Debug.Assert(SqlCommandColumnEncryptionSetting.UseConnectionSetting == columnEncryptionSetting, "Unexpected value for command level override"); - return (connection != null && connection.ConnectionOptions != null && - connection.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled); + return (connection != null && connection.ConnectionOptions != null + && connection.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled); } } - internal static object GetNullSqlValue( - SqlBuffer nullVal, - SqlMetaDataPriv md, - SqlCommandColumnEncryptionSetting columnEncryptionSetting, - SqlInternalConnectionTds connection) + internal static object GetNullSqlValue(SqlBuffer nullVal, + SqlMetaDataPriv md, + SqlCommandColumnEncryptionSetting columnEncryptionSetting, + SqlInternalConnectionTds connection) { SqlDbType type = md.type; @@ -6586,6 +6046,11 @@ internal static object GetNullSqlValue( nullVal.SetToNullOfType(SqlBuffer.StorageType.Json); break; + case SqlDbTypeExtensions.Vector: + nullVal.SetToNullOfType(SqlBuffer.StorageType.Vector); + nullVal.SetVectorInfo(MetaType.GetVectorElementCount(md.length, md.scale), md.scale, true); + break; + default: Debug.Fail("unknown null sqlType!" + md.type.ToString()); break; @@ -6627,7 +6092,7 @@ internal TdsOperationStatus TrySkipValue(SqlMetaDataPriv md, int columnOrdinal, TdsOperationStatus result; if (md.metaType.IsPlp) { - result = TrySkipPlpValue(UInt64.MaxValue, stateObj, out _); + result = TrySkipPlpValue(ulong.MaxValue, stateObj, out _); if (result != TdsOperationStatus.Done) { return result; @@ -6736,7 +6201,7 @@ private TdsOperationStatus TryReadSqlStringValue(SqlBuffer value, byte type, int break; case TdsEnums.SQLJSON: - encoding = Encoding.UTF8; + encoding = s_utf8EncodingWithoutBom; string jsonStringValue; result = stateObj.TryReadStringWithEncoding(length, encoding, isPlp, out jsonStringValue); if (result != TdsOperationStatus.Done) @@ -6750,13 +6215,13 @@ private TdsOperationStatus TryReadSqlStringValue(SqlBuffer value, byte type, int case TdsEnums.SQLNVARCHAR: case TdsEnums.SQLNTEXT: { - String s = null; + string s = null; if (isPlp) { char[] cc = null; - result = TryReadPlpUnicodeChars(ref cc, 0, length >> 1, stateObj, out length); + if (result != TdsOperationStatus.Done) { return result; @@ -6767,7 +6232,7 @@ private TdsOperationStatus TryReadSqlStringValue(SqlBuffer value, byte type, int } else { - s = ""; + s = string.Empty; } } else @@ -6830,7 +6295,7 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt return false; } - longValue = BitConverter.ToInt64(unencryptedBytes, 0); + longValue = BinaryPrimitives.ReadInt64LittleEndian(unencryptedBytes); if (tdsType == TdsEnums.SQLBIT || tdsType == TdsEnums.SQLBITN) @@ -6868,7 +6333,7 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt return false; } - singleValue = BitConverter.ToSingle(unencryptedBytes, 0); + singleValue = BitConverterCompatible.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(unencryptedBytes)); value.Single = singleValue; break; @@ -6879,7 +6344,7 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt return false; } - doubleValue = BitConverter.ToDouble(unencryptedBytes, 0); + doubleValue = BitConverter.Int64BitsToDouble(BinaryPrimitives.ReadInt64LittleEndian(unencryptedBytes)); value.Double = doubleValue; break; @@ -6896,8 +6361,8 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt return false; } - mid = BitConverter.ToInt32(unencryptedBytes, 0); - lo = BitConverter.ToUInt32(unencryptedBytes, 4); + mid = BinaryPrimitives.ReadInt32LittleEndian(unencryptedBytes); + lo = BinaryPrimitives.ReadUInt32LittleEndian(unencryptedBytes.AsSpan(4)); long l = (((long)mid) << 0x20) + ((long)lo); value.SetToMoney(l); @@ -6934,8 +6399,8 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt return false; } - daypart = BitConverter.ToInt32(unencryptedBytes, 0); - timepart = BitConverter.ToUInt32(unencryptedBytes, 4); + daypart = BinaryPrimitives.ReadInt32LittleEndian(unencryptedBytes); + timepart = BinaryPrimitives.ReadUInt32LittleEndian(unencryptedBytes.AsSpan(4)); value.SetToDateTime(daypart, (int)timepart); break; @@ -7072,12 +6537,12 @@ internal bool DeserializeUnencryptedValue(SqlBuffer value, byte[] unencryptedByt } internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, - SqlMetaDataPriv md, - int length, - TdsParserStateObject stateObj, - SqlCommandColumnEncryptionSetting columnEncryptionOverride, - string columnName, - SqlCommand command = null) + SqlMetaDataPriv md, + int length, + TdsParserStateObject stateObj, + SqlCommandColumnEncryptionSetting columnEncryptionOverride, + string columnName, + SqlCommand command = null) { bool isPlp = md.metaType.IsPlp; byte tdsType = md.tdsType; @@ -7087,7 +6552,7 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, if (isPlp) { // We must read the column value completely, no matter what length is passed in - length = Int32.MaxValue; + length = int.MaxValue; } //DEVNOTE: When modifying the following routines (for deserialization) please pay attention to @@ -7134,8 +6599,8 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, } if (md.isEncrypted - && ((columnEncryptionOverride == SqlCommandColumnEncryptionSetting.Enabled - || columnEncryptionOverride == SqlCommandColumnEncryptionSetting.ResultSetOnly) + && (columnEncryptionOverride == SqlCommandColumnEncryptionSetting.Enabled + || columnEncryptionOverride == SqlCommandColumnEncryptionSetting.ResultSetOnly || (columnEncryptionOverride == SqlCommandColumnEncryptionSetting.UseConnectionSetting && _connHandler != null && _connHandler.ConnectionOptions != null && _connHandler.ConnectionOptions.ColumnEncryptionSetting == SqlConnectionColumnEncryptionSetting.Enabled))) @@ -7190,10 +6655,32 @@ internal TdsOperationStatus TryReadSqlValue(SqlBuffer value, } else { - value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(b, true); // doesn't copy the byte array + value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(b, true); // doesn't copy the byte array } break; + case TdsEnums.SQLVECTOR: + // Vector data is read as non-plp binary value. + // This is same as reading varbinary(8000). + byte[] buff = new byte[length]; + result = stateObj.TryReadByteArray(buff, length); + if (result != TdsOperationStatus.Done) + { + return result; + } + + // Internally, we use Sqlbinary to deal with varbinary data and store it in + // SqlBuffer as SqlBinary value. + value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(buff, true); + + // Extract the metadata from the payload and set it as the vector attributes + // in the SqlBuffer. This metadata is further used when constructing a SqlVector + // object from binary payload. + int elementCount = BinaryPrimitives.ReadUInt16LittleEndian(buff.AsSpan(2)); + byte elementType = buff[4]; + value.SetVectorInfo(elementCount, elementType, false); + break; + case TdsEnums.SQLCHAR: case TdsEnums.SQLBIGCHAR: case TdsEnums.SQLVARCHAR: @@ -7503,6 +6990,7 @@ internal TdsOperationStatus TryReadSqlValueInternal(SqlBuffer value, byte tdsTyp case TdsEnums.SQLBIGVARBINARY: case TdsEnums.SQLVARBINARY: case TdsEnums.SQLIMAGE: + case TdsEnums.SQLVECTOR: { // Note: Better not come here with plp data!! Debug.Assert(length <= TdsEnums.MAXSIZE); @@ -7512,7 +7000,7 @@ internal TdsOperationStatus TryReadSqlValueInternal(SqlBuffer value, byte tdsTyp { return result; } - value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(b, true); // doesn't copy the byte array + value.SqlBinary = SqlTypeWorkarounds.SqlBinaryCtor(b, true); // doesn't copy the byte array break; } @@ -7546,7 +7034,6 @@ internal TdsOperationStatus TryReadSqlValueInternal(SqlBuffer value, byte tdsTyp // } internal TdsOperationStatus TryReadSqlVariant(SqlBuffer value, int lenTotal, TdsParserStateObject stateObj) { - Debug.Assert(_is2000 == true, "Shouldn't be dealing with sql_variaint in pre-SQL2000 server!"); // get the SQLVariant type byte type; TdsOperationStatus result = stateObj.TryReadByte(out type); @@ -7750,8 +7237,6 @@ internal TdsOperationStatus TryReadSqlVariant(SqlBuffer value, int lenTotal, Tds // internal Task WriteSqlVariantValue(object value, int length, int offset, TdsParserStateObject stateObj, bool canAccumulate = true) { - Debug.Assert(_is2000 == true, "Shouldn't be dealing with sql_variant in pre-SQL2000 server!"); - // handle null values if (ADP.IsNull(value)) { @@ -7791,23 +7276,23 @@ internal Task WriteSqlVariantValue(object value, int length, int offset, TdsPars switch (mt.TDSType) { case TdsEnums.SQLFLT4: - WriteFloat((Single)value, stateObj); + WriteFloat((float)value, stateObj); break; case TdsEnums.SQLFLT8: - WriteDouble((Double)value, stateObj); + WriteDouble((double)value, stateObj); break; case TdsEnums.SQLINT8: - WriteLong((Int64)value, stateObj); + WriteLong((long)value, stateObj); break; case TdsEnums.SQLINT4: - WriteInt((Int32)value, stateObj); + WriteInt((int)value, stateObj); break; case TdsEnums.SQLINT2: - WriteShort((Int16)value, stateObj); + WriteShort((short)value, stateObj); break; case TdsEnums.SQLINT1: @@ -7874,15 +7359,15 @@ internal Task WriteSqlVariantValue(object value, int length, int offset, TdsPars case TdsEnums.SQLMONEY: { - WriteCurrency((Decimal)value, 8, stateObj); + WriteCurrency((decimal)value, 8, stateObj); break; } case TdsEnums.SQLNUMERICN: { stateObj.WriteByte(mt.Precision); //propbytes: precision - stateObj.WriteByte((byte)((Decimal.GetBits((Decimal)value)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale - WriteDecimal((Decimal)value, stateObj); + stateObj.WriteByte((byte)((decimal.GetBits((decimal)value)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale + WriteDecimal((decimal)value, stateObj); break; } @@ -7919,8 +7404,6 @@ internal Task WriteSqlVariantValue(object value, int length, int offset, TdsPars // internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject stateObj, bool canAccumulate = true) { - Debug.Assert(_is2000 == true, "Shouldn't be dealing with sql_variant in pre-SQL2000 server!"); - // handle null values if (value == null || (DBNull.Value == value)) { @@ -7940,27 +7423,27 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta { case TdsEnums.SQLFLT4: WriteSqlVariantHeader(6, metatype.TDSType, metatype.PropBytes, stateObj); - WriteFloat((Single)value, stateObj); + WriteFloat((float)value, stateObj); break; case TdsEnums.SQLFLT8: WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); - WriteDouble((Double)value, stateObj); + WriteDouble((double)value, stateObj); break; case TdsEnums.SQLINT8: WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); - WriteLong((Int64)value, stateObj); + WriteLong((long)value, stateObj); break; case TdsEnums.SQLINT4: WriteSqlVariantHeader(6, metatype.TDSType, metatype.PropBytes, stateObj); - WriteInt((Int32)value, stateObj); + WriteInt((int)value, stateObj); break; case TdsEnums.SQLINT2: WriteSqlVariantHeader(4, metatype.TDSType, metatype.PropBytes, stateObj); - WriteShort((Int16)value, stateObj); + WriteShort((short)value, stateObj); break; case TdsEnums.SQLINT1: @@ -8039,7 +7522,7 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta case TdsEnums.SQLMONEY: { WriteSqlVariantHeader(10, metatype.TDSType, metatype.PropBytes, stateObj); - WriteCurrency((Decimal)value, 8, stateObj); + WriteCurrency((decimal)value, 8, stateObj); break; } @@ -8047,8 +7530,8 @@ internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject sta { WriteSqlVariantHeader(21, metatype.TDSType, metatype.PropBytes, stateObj); stateObj.WriteByte(metatype.Precision); //propbytes: precision - stateObj.WriteByte((byte)((Decimal.GetBits((Decimal)value)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale - WriteDecimal((Decimal)value, stateObj); + stateObj.WriteByte((byte)((decimal.GetBits((decimal)value)[3] & 0x00ff0000) >> 0x10)); // propbytes: scale + WriteDecimal((decimal)value, stateObj); break; } @@ -8104,7 +7587,7 @@ private byte[] SerializeSqlMoney(SqlMoney value, int length, TdsParserStateObjec private void WriteSqlMoney(SqlMoney value, int length, TdsParserStateObject stateObj) { // UNDONE: can I use SqlMoney.ToInt64()? - int[] bits = Decimal.GetBits(value.Value); + int[] bits = decimal.GetBits(value.Value); // this decimal should be scaled by 10000 (regardless of what the incoming decimal was scaled by) bool isNeg = (0 != (bits[3] & unchecked((int)0x80000000))); @@ -8115,7 +7598,7 @@ private void WriteSqlMoney(SqlMoney value, int length, TdsParserStateObject stat if (length == 4) { - Decimal decimalValue = value.Value; + decimal decimalValue = value.Value; // validate the value can be represented as a small money if (decimalValue < TdsEnums.SQL_SMALL_MONEY_MIN || decimalValue > TdsEnums.SQL_SMALL_MONEY_MAX) @@ -8175,10 +7658,10 @@ private byte[] SerializeCurrency(Decimal value, int length, TdsParserStateObject return bytes; } - private void WriteCurrency(Decimal value, int length, TdsParserStateObject stateObj) + private void WriteCurrency(decimal value, int length, TdsParserStateObject stateObj) { SqlMoney m = new SqlMoney(value); - int[] bits = Decimal.GetBits(m.Value); + int[] bits = decimal.GetBits(m.Value); // this decimal should be scaled by 10000 (regardless of what the incoming decimal was scaled by) bool isNeg = (0 != (bits[3] & unchecked((int)0x80000000))); @@ -8296,7 +7779,7 @@ private byte[] SerializeDateTimeOffset(DateTimeOffset value, byte scale, int len private void WriteDateTimeOffset(DateTimeOffset value, byte scale, int length, TdsParserStateObject stateObj) { WriteDateTime2(value.UtcDateTime, scale, length - 2, stateObj); - Int16 offset = (Int16)value.Offset.TotalMinutes; + short offset = (short)value.Offset.TotalMinutes; stateObj.WriteByte((byte)(offset & 0xff)); stateObj.WriteByte((byte)((offset >> 8) & 0xff)); } @@ -8373,7 +7856,7 @@ internal static SqlDecimal AdjustSqlDecimalScale(SqlDecimal d, int newScale) internal static decimal AdjustDecimalScale(decimal value, int newScale) { - int oldScale = (Decimal.GetBits(value)[3] & 0x00ff0000) >> 0x10; + int oldScale = (decimal.GetBits(value)[3] & 0x00ff0000) >> 0x10; if (newScale != oldScale) { @@ -8488,7 +7971,7 @@ struct { private void WriteDecimal(decimal value, TdsParserStateObject stateObj) { - stateObj._decimalBits = Decimal.GetBits(value); + stateObj._decimalBits = decimal.GetBits(value); Debug.Assert(stateObj._decimalBits != null, "decimalBits should be filled in at TdsExecuteRPC time"); /* @@ -8618,12 +8101,12 @@ internal Task WriteString(string s, int length, int offset, TdsParserStateObject } - private unsafe static void CopyCharsToBytes(char[] source, int sourceOffset, byte[] dest, int destOffset, int charLength) + private static void CopyCharsToBytes(char[] source, int sourceOffset, byte[] dest, int destOffset, int charLength) { Buffer.BlockCopy(source, sourceOffset, dest, destOffset, charLength * ADP.CharSize); } - private unsafe static void CopyStringToBytes(string source, int sourceOffset, byte[] dest, int destOffset, int charLength) + private static void CopyStringToBytes(string source, int sourceOffset, byte[] dest, int destOffset, int charLength) { Encoding.Unicode.GetBytes(source, sourceOffset, charLength, dest, destOffset); } @@ -8718,7 +8201,7 @@ internal int GetEncodingCharLength(string value, int numChars, int charOffset, E internal TdsOperationStatus TryGetDataLength(SqlMetaDataPriv colmeta, TdsParserStateObject stateObj, out ulong length) { // Handle 2005 specific tokens - if (_is2005 && colmeta.metaType.IsPlp) + if (colmeta.metaType.IsPlp) { Debug.Assert(colmeta.tdsType == TdsEnums.SQLXMLTYPE || colmeta.tdsType == TdsEnums.SQLBIGVARCHAR || @@ -8765,36 +8248,46 @@ internal TdsOperationStatus TryGetTokenLength(byte token, TdsParserStateObject s } TdsOperationStatus result; - if (_is2005) - { // Handle 2005 specific exceptions - 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) + + 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) { - 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; + tokenLength = 0; + return result; } - else if (token == TdsEnums.SQLJSON) + 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) { - tokenLength = -1; - return TdsOperationStatus.Done; + tokenLength = 0; + return result; } + tokenLength = value; + return TdsOperationStatus.Done; } switch (token & TdsEnums.SQLLenMask) @@ -9123,7 +8616,7 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD if (write) { - // Write Feature ID, legth of the version# field and Sensitivity Classification Version# + // 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); @@ -9134,11 +8627,11 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD internal int WriteTceFeatureRequest(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 = Version if (write) { - // Write Feature ID, legth of the version# field and TCE Version# + // 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); @@ -9171,9 +8664,9 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_AZURESQLSUPPORT); // Feature Data length - WriteInt(s_FeatureExtDataAzureSQLSupportFeatureRequest.Length, _physicalStateObj); + WriteInt(s_featureExtDataAzureSQLSupportFeatureRequest.Length, _physicalStateObj); - _physicalStateObj.WriteByteArray(s_FeatureExtDataAzureSQLSupportFeatureRequest, s_FeatureExtDataAzureSQLSupportFeatureRequest.Length, 0); + _physicalStateObj.WriteByteArray(s_featureExtDataAzureSQLSupportFeatureRequest, s_featureExtDataAzureSQLSupportFeatureRequest.Length, 0); } return len; @@ -9225,6 +8718,36 @@ internal int WriteFedAuthFeatureRequest(FederatedAuthenticationFeatureExtensionD return len; } + /// + /// Writes the Vector Support feature request to the physical state object. + /// The request includes the feature ID, feature data length, and version number. + /// + /// If true, writes the feature request to the physical state object. + /// The length of the feature request in bytes. + /// + /// The feature request consists of: + /// - 1 byte for the feature ID. + /// - 4 bytes for the feature data length. + /// - 1 byte for the version number. + /// + internal int WriteVectorSupportFeatureRequest(bool write) + { + const int len = 6; + + if (write) + { + // Write Feature ID + _physicalStateObj.WriteByte(TdsEnums.FEATUREEXT_VECTORSUPPORT); + + // Feature Data Length + WriteInt(1, _physicalStateObj); + + _physicalStateObj.WriteByte(TdsEnums.MAX_SUPPORTED_VECTOR_VERSION); + } + + return len; + } + private void WriteLoginData(SqlLogin rec, TdsEnums.FeatureExtension requestedFeatures, SessionData recoverySessionData, @@ -9239,8 +8762,7 @@ private void WriteLoginData(SqlLogin rec, int length, int featureExOffset, string clientInterfaceName, - byte[] outSSPIBuff, - uint outSSPILength) + ReadOnlySpan outSSPI) { try { @@ -9412,8 +8934,8 @@ private void WriteLoginData(SqlLogin rec, WriteShort(offset, _physicalStateObj); // ibSSPI offset if (rec.useSSPI) { - WriteShort((int)outSSPILength, _physicalStateObj); - offset += (int)outSSPILength; + WriteShort(outSSPI.Length, _physicalStateObj); + offset += outSSPI.Length; } else { @@ -9472,7 +8994,7 @@ private void WriteLoginData(SqlLogin rec, // send over SSPI data if we are using SSPI if (rec.useSSPI) - _physicalStateObj.WriteByteArray(outSSPIBuff, (int)outSSPILength, 0); + _physicalStateObj.WriteByteSpan(outSSPI); WriteString(rec.attachDBFilename, _physicalStateObj); if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) @@ -9558,6 +9080,11 @@ private int ApplyFeatureExData(TdsEnums.FeatureExtension requestedFeatures, length += WriteJsonSupportFeatureRequest(write); } + if ((requestedFeatures & TdsEnums.FeatureExtension.VectorSupport) != 0) + { + length += WriteVectorSupportFeatureRequest(write); + } + length++; // for terminator if (write) { @@ -9625,7 +9152,7 @@ internal byte[] GetDTCAddress(int timeout, TdsParserStateObject stateObj) long dtcLength = dtcReader.GetBytes(0, 0, null, 0, 0); // - if (dtcLength <= Int32.MaxValue) + if (dtcLength <= int.MaxValue) { int cb = (int)dtcLength; @@ -9710,17 +9237,14 @@ internal SqlDataReader TdsExecuteTransactionManagerRequest( stateObj.SniContext = SniContext.Snix_Execute; - if (_is2005) - { - const int marsHeaderSize = 18; // 4 + 2 + 8 + 4 - const int totalHeaderLength = 22; // 4 + 4 + 2 + 8 + 4 - Debug.Assert(stateObj._outBytesUsed == stateObj._outputHeaderLen, "Output bytes written before total header length"); - // Write total header length - WriteInt(totalHeaderLength, stateObj); - // Write mars header length - WriteInt(marsHeaderSize, stateObj); - WriteMarsHeaderData(stateObj, _currentTransaction); - } + const int marsHeaderSize = 18; // 4 + 2 + 8 + 4 + const int totalHeaderLength = 22; // 4 + 4 + 2 + 8 + 4 + Debug.Assert(stateObj._outBytesUsed == stateObj._outputHeaderLen, "Output bytes written before total header length"); + // Write total header length + WriteInt(totalHeaderLength, stateObj); + // Write mars header length + WriteInt(marsHeaderSize, stateObj); + WriteMarsHeaderData(stateObj, _currentTransaction); WriteShort((short)request, stateObj); // write TransactionManager Request type @@ -9745,7 +9269,6 @@ internal SqlDataReader TdsExecuteTransactionManagerRequest( } break; case TdsEnums.TransactionManagerRequestType.Begin: - Debug.Assert(Is2005OrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-2005 clients for BeginTransaction!"); Debug.Assert(transaction != null, "Should have specified an internalTransaction when doing a BeginTransaction request!"); // Only assign the passed in transaction if it is not equal to the current transaction. @@ -9774,13 +9297,11 @@ internal SqlDataReader TdsExecuteTransactionManagerRequest( WriteString(transactionName, stateObj); break; case TdsEnums.TransactionManagerRequestType.Promote: - Debug.Assert(Is2005OrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-2005 clients for PromoteTransaction!"); // No payload - except current transaction in header // Promote returns a DTC cookie. However, the transaction cookie we use for the // connection does not change after a promote. break; case TdsEnums.TransactionManagerRequestType.Commit: - Debug.Assert(Is2005OrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-2005 clients for CommitTransaction!"); Debug.Assert(transactionName.Length == 0, "Should not have a transaction name on Commit"); stateObj.WriteByte((byte)0); // No xact name @@ -9792,7 +9313,6 @@ internal SqlDataReader TdsExecuteTransactionManagerRequest( // WriteByte((byte) 0, stateObj); // No begin xact name break; case TdsEnums.TransactionManagerRequestType.Rollback: - Debug.Assert(Is2005OrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-2005 clients for RollbackTransaction!"); stateObj.WriteByte((byte)(transactionName.Length * 2)); // Write number of bytes (unicode string). WriteString(transactionName, stateObj); @@ -9804,7 +9324,6 @@ internal SqlDataReader TdsExecuteTransactionManagerRequest( // WriteByte((byte) 0, stateObj); // No begin xact name break; case TdsEnums.TransactionManagerRequestType.Save: - Debug.Assert(Is2005OrNewer, "Should not be calling TdsExecuteTransactionManagerRequest on pre-2005 clients for SaveTransaction!"); stateObj.WriteByte((byte)(transactionName.Length * 2)); // Write number of bytes (unicode string). WriteString(transactionName, stateObj); @@ -9849,7 +9368,6 @@ internal SqlDataReader TdsExecuteTransactionManagerRequest( } catch (Exception e) { - // UNDONE - should not be catching all exceptions!!! if (!ADP.IsCatchableExceptionType(e)) { throw; @@ -9907,7 +9425,7 @@ internal void FailureCleanup(TdsParserStateObject stateObj, Exception e) } finally { - // Reset the ThreadHasParserLock value incase our caller expects it to be set\not set + // Reset the ThreadHasParserLock value in case our caller expects it to be set\not set _connHandler.ThreadHasParserLockForClose = originalThreadHasParserLock; } } @@ -9972,11 +9490,7 @@ internal Task TdsExecuteSQLBatch(string text, int timeout, SqlNotificationReques } stateObj.SniContext = SniContext.Snix_Execute; - if (_is2005) - { - - WriteRPCBatchHeaders(stateObj, notificationRequest); - } + WriteRPCBatchHeaders(stateObj, notificationRequest); stateObj._outputMessageType = TdsEnums.MT_SQL; @@ -10026,7 +9540,6 @@ internal Task TdsExecuteSQLBatch(string text, int timeout, SqlNotificationReques } catch (Exception e) { - //Debug.Assert(_state == TdsParserState.Broken, "Caught exception in TdsExecuteSQLBatch but connection was not broken!"); // UNDONE - should not be catching all exceptions!!! if (!ADP.IsCatchableExceptionType(e)) { @@ -10103,11 +9616,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout stateObj.SniContext = SniContext.Snix_Execute; - if (_is2005) - { - WriteRPCBatchHeaders(stateObj, notificationRequest); - } stateObj._outputMessageType = TdsEnums.MT_RPC; } @@ -10118,7 +9627,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout if (startParam == 0 || ii > startRpc) { - if (rpcext.ProcID != 0 && _is2000) + if (rpcext.ProcID != 0) { // Perf optimization for 2000 and later, Debug.Assert(rpcext.ProcID < 255, "rpcExec:ProcID can't be larger than 255"); @@ -10164,8 +9673,7 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout if ( !(cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.Enabled || - (cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && cmd.Connection.IsColumnEncryptionSettingEnabled)) - ) + (cmd.ColumnEncryptionSetting == SqlCommandColumnEncryptionSetting.UseConnectionSetting && cmd.Connection.IsColumnEncryptionSettingEnabled))) { throw SQL.ParamInvalidForceColumnEncryptionSetting(param.ParameterName, rpcext.GetCommandTextOrRpcName()); } @@ -10195,582 +9703,639 @@ internal Task TdsExecuteRPC(SqlCommand cmd, IList<_SqlRPC> rpcArray, int timeout continue; } - if ((!_is2000 && !mt.Is70Supported) || - (!_is2005 && !mt.Is80Supported) || - (!_is2008 && !mt.Is90Supported)) + if (!_is2008 && !mt.Is90Supported) { throw ADP.VersionDoesNotSupportDataType(mt.TypeName); } - object value = null; - bool isNull = true; - bool isSqlVal = false; - bool isDataFeed = false; - // if we have an output param, set the value to null so we do not send it across to the server - if (parameterDirection == ParameterDirection.Output) - { - isSqlVal = param.ParameterIsSqlType; // We have to forward the TYPE info, we need to know what type we are returning. Once we null the parameter we will no longer be able to distinguish what type were seeing. - param.Value = null; - param.ParameterIsSqlType = isSqlVal; - } - else - { - value = param.GetCoercedValue(); - isNull = param.IsNull; - if (!isNull) - { - isSqlVal = param.CoercedValueIsSqlType; - isDataFeed = param.CoercedValueIsDataFeed; - } - } - - WriteParameterName(param.ParameterName, stateObj, enableOptimizedParameterBinding); - - // Write parameter status - stateObj.WriteByte(options); - // MaxLen field is only written out for non-fixed length data types - // use the greater of the two sizes for maxLen - int actualSize; - int size = mt.IsSizeInCharacters ? param.GetParameterSize() * 2 : param.GetParameterSize(); + Task writeParamTask = TDSExecuteRPCAddParameter(stateObj, param, mt, options, cmd, enableOptimizedParameterBinding); - //for UDTs, we calculate the length later when we get the bytes. This is a really expensive operation - if (mt.TDSType != TdsEnums.SQLUDT) - // getting the actualSize is expensive, cache here and use below - actualSize = param.GetActualSize(); - else - actualSize = 0; //get this later - - byte precision = 0; - byte scale = 0; - - // scale and precision are only relevant for numeric and decimal types - // adjust the actual value scale and precision to match the user specified - if (mt.SqlDbType == SqlDbType.Decimal) + if (!sync) { - precision = param.GetActualPrecision(); - scale = param.GetActualScale(); - - if (precision > TdsEnums.MAX_NUMERIC_PRECISION) + if (writeParamTask == null) { - throw SQL.PrecisionValueOutOfRange(precision); + writeParamTask = stateObj.WaitForAccumulatedWrites(); } - // bug 49512, make sure the value matches the scale the user enters - if (!isNull) + if (writeParamTask != null) { - if (isSqlVal) + Task task = null; + if (completion == null) { - value = AdjustSqlDecimalScale((SqlDecimal)value, scale); - - // If Precision is specified, verify value precision vs param precision - if (precision != 0) - { - if (precision < ((SqlDecimal)value).Precision) - { - throw ADP.ParameterValueOutOfRange((SqlDecimal)value); - } - } + completion = new TaskCompletionSource(); + task = completion.Task; } - else - { - value = AdjustDecimalScale((Decimal)value, scale); - SqlDecimal sqlValue = new SqlDecimal((Decimal)value); + TDSExecuteRPCParameterSetupWriteCompletion( + cmd, + rpcArray, + timeout, + inSchema, + notificationRequest, + stateObj, + isCommandProc, + sync, + completion, + ii, + i + 1, + writeParamTask + ); - // If Precision is specified, verify value precision vs param precision - if (precision != 0) - { - if (precision < sqlValue.Precision) - { - throw ADP.ParameterValueOutOfRange((Decimal)value); - } - } + // Take care of releasing the locks + if (releaseConnectionLock) + { + task.ContinueWith( + static (Task _, object state) => ((SqlInternalConnectionTds)state)._parserLock.Release(), + state: _connHandler, + TaskScheduler.Default + ); + releaseConnectionLock = false; } + + return task; } } - - bool isParameterEncrypted = 0 != (options & TdsEnums.RPC_PARAM_ENCRYPTED); - - // Additional information we need to send over wire to the server when writing encrypted parameters. - SqlColumnEncryptionInputParameterInfo encryptedParameterInfoToWrite = null; - - // If the parameter is encrypted, we need to encrypt the value. - if (isParameterEncrypted) +#if DEBUG + else { - Debug.Assert(mt.TDSType != TdsEnums.SQLVARIANT && - mt.TDSType != TdsEnums.SQLUDT && - mt.TDSType != TdsEnums.SQLXMLTYPE && - mt.TDSType != TdsEnums.SQLIMAGE && - mt.TDSType != TdsEnums.SQLTEXT && - mt.TDSType != TdsEnums.SQLNTEXT && - mt.TDSType != TdsEnums.SQLJSON, "Type unsupported for encryption"); - - byte[] serializedValue = null; - byte[] encryptedValue = null; - - if (!isNull) - { - try - { - if (isSqlVal) - { - serializedValue = SerializeUnencryptedSqlValue(value, mt, actualSize, param.Offset, param.NormalizationRuleVersion, stateObj); - } - else - { - // for codePageEncoded types, WriteValue simply expects the number of characters - // For plp types, we also need the encoded byte size - serializedValue = SerializeUnencryptedValue(value, mt, param.GetActualScale(), actualSize, param.Offset, isDataFeed, param.NormalizationRuleVersion, stateObj); - } + Debug.Assert(writeParamTask == null, "Should not have a task when executing sync"); + } +#endif + } // parameter for loop - Debug.Assert(serializedValue != null, "serializedValue should not be null in TdsExecuteRPC."); - encryptedValue = SqlSecurityUtility.EncryptWithKey(serializedValue, param.CipherMetadata, _connHandler.Connection, cmd); - } - catch (Exception e) - { - throw SQL.ParamEncryptionFailed(param.ParameterName, null, e); - } + // If this is not the last RPC we are sending, add the batch flag + if (ii < (rpcArray.Count - 1)) + { + stateObj.WriteByte(TdsEnums.SQL2005_RPCBATCHFLAG); + } + } // rpc for loop - Debug.Assert(encryptedValue != null && encryptedValue.Length > 0, - "encryptedValue should not be null or empty in TdsExecuteRPC."); - } - else - { - encryptedValue = null; - } + Task execFlushTask = stateObj.ExecuteFlush(); + Debug.Assert(!sync || execFlushTask == null, "Should not get a task when executing sync"); + if (execFlushTask != null) + { + Task task = null; - // Change the datatype to varbinary(max). - // Since we don't know the size of the encrypted parameter on the server side, always set to (max). - // - mt = MetaType.MetaMaxVarBinary; - size = -1; - actualSize = (encryptedValue == null) ? 0 : encryptedValue.Length; + if (completion == null) + { + completion = new TaskCompletionSource(); + task = completion.Task; + } - encryptedParameterInfoToWrite = new SqlColumnEncryptionInputParameterInfo(param.GetMetadataForTypeInfo(), - param.CipherMetadata); + TDSExecuteRPCParameterSetupFlushCompletion(stateObj, completion, execFlushTask, releaseConnectionLock); - // Set the value to the encrypted value and mark isSqlVal as false for VARBINARY encrypted value. - value = encryptedValue; - isSqlVal = false; - } + // TDSExecuteRPCParameterSetupFlushCompletion calling ExecuteFlushTaskCallback will take care of the locks for us + releaseConnectionLock = false; - Debug.Assert(isParameterEncrypted == (encryptedParameterInfoToWrite != null), - "encryptedParameterInfoToWrite can be not null if and only if isParameterEncrypted is true."); + return task; + } + } + catch (Exception e) + { + FailureCleanup(stateObj, e); + throw; + } + FinalizeExecuteRPC(stateObj); + if (completion != null) + { + completion.SetResult(null); + } + return null; + } + catch (Exception e) + { + FinalizeExecuteRPC(stateObj); + if (completion != null) + { + completion.SetException(e); + return null; + } + else + { + throw; + } + } + finally + { + Debug.Assert(firstCall || !releaseConnectionLock, "Shouldn't be releasing locks synchronously after the first call"); + if (releaseConnectionLock) + { + _connHandler._parserLock.Release(); + } + } + } - Debug.Assert(!isSqlVal || !isParameterEncrypted, "isParameterEncrypted can be true only if isSqlVal is false."); + private Task TDSExecuteRPCAddParameter(TdsParserStateObject stateObj, SqlParameter param, MetaType mt, byte options, SqlCommand command, bool isAnonymous) + { + int tempLen; + object value = null; + bool isNull = true; + bool isSqlVal = false; + bool isDataFeed = false; + // if we have an output param, set the value to null so we do not send it across to the server + if (param.Direction == ParameterDirection.Output) + { + isSqlVal = param.ParameterIsSqlType; // We have to forward the TYPE info, we need to know what type we are returning. Once we null the parameter we will no longer be able to distinguish what type were seeing. - // fixup the types by using the NullableType property of the MetaType class - // - // following rules should be followed based on feedback from the M-SQL team - // 1) always use the BIG* types (ex: instead of SQLCHAR use SQLBIGCHAR) - // 2) always use nullable types (ex: instead of SQLINT use SQLINTN) - // 3) DECIMALN should always be sent as NUMERICN - // - stateObj.WriteByte(mt.NullableType); + // Output parameter of SqlDbType vector are defined through SqlParameter.Value. + // This check ensures that we do not discard the parameter value when SqlDbType is vector. + if (mt.SqlDbType != SqlDbTypeExtensions.Vector) + { + param.Value = null; + } + param.ParameterIsSqlType = isSqlVal; + } + else + { + value = param.GetCoercedValue(); + isNull = param.IsNull; + if (!isNull) + { + isSqlVal = param.CoercedValueIsSqlType; + isDataFeed = param.CoercedValueIsDataFeed; + } + } - // handle variants here: the SQLVariant writing routine will write the maxlen and actual len columns - if (mt.TDSType == TdsEnums.SQLVARIANT) - { - // devnote: Do we ever hit this codepath? Yes, when a null value is being written out via a sql variant - // param.GetActualSize is not used - WriteSqlVariantValue(isSqlVal ? MetaType.GetComValueFromSqlVariant(value) : value, param.GetActualSize(), param.Offset, stateObj); - continue; - } + WriteParameterName(param.ParameterName, stateObj, isAnonymous); - int codePageByteSize = 0; - int maxsize = 0; + // Write parameter status + stateObj.WriteByte(options); - if (mt.IsAnsiType) - { - // Avoid the following code block if ANSI but unfilled LazyMat blob - if ((!isNull) && (!isDataFeed)) - { - string s; + // MaxLen field is only written out for non-fixed length data types + // use the greater of the two sizes for maxLen + int actualSize; + int size = mt.IsSizeInCharacters ? param.GetParameterSize() * 2 : param.GetParameterSize(); - if (isSqlVal) - { - if (value is SqlString) - { - s = ((SqlString)value).Value; - } - else - { - Debug.Assert(value is SqlChars, "Unknown value for Ansi datatype"); - s = new String(((SqlChars)value).Value); - } - } - else - { - s = (string)value; - } + //for UDTs, we calculate the length later when we get the bytes. This is a really expensive operation + if (mt.TDSType != TdsEnums.SQLUDT) + // getting the actualSize is expensive, cache here and use below + actualSize = param.GetActualSize(); + else + actualSize = 0; //get this later - codePageByteSize = GetEncodingCharLength(s, actualSize, param.Offset, _defaultEncoding); - } + byte precision = 0; + byte scale = 0; - if (mt.IsPlp) - { - WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj); - } - else - { - maxsize = (size > codePageByteSize) ? size : codePageByteSize; - if (maxsize == 0) - { - // 2005 doesn't like 0 as MaxSize. Change it to 2 for unicode types (SQL9 - 682322) - if (mt.IsNCharType) - maxsize = 2; - else - maxsize = 1; - } + // scale and precision are only relevant for numeric and decimal types + // adjust the actual value scale and precision to match the user specified + if (mt.SqlDbType == SqlDbType.Decimal) + { + precision = param.GetActualPrecision(); + scale = param.GetActualScale(); - WriteParameterVarLen(mt, maxsize, false/*IsNull*/, stateObj); - } - } - else - { - // If type timestamp - treat as fixed type and always send over timestamp length, which is 8. - // For fixed types, we either send null or fixed length for type length. We want to match that - // behavior for timestamps. However, in the case of null, we still must send 8 because if we - // send null we will not receive a output val. You can send null for fixed types and still - // receive a output value, but not for variable types. So, always send 8 for timestamp because - // while the user sees it as a fixed type, we are actually representing it as a bigbinary which - // is variable. - if (mt.SqlDbType == SqlDbType.Timestamp) - { - WriteParameterVarLen(mt, TdsEnums.TEXT_TIME_STAMP_LEN, false, stateObj); - } - else if (mt.SqlDbType == SqlDbType.Udt) - { - byte[] udtVal = null; - Format format = Format.Native; + if (precision > TdsEnums.MAX_NUMERIC_PRECISION) + { + throw SQL.PrecisionValueOutOfRange(precision); + } - Debug.Assert(_is2005, "Invalid DataType UDT for non-2005 or later server!"); + // bug 49512, make sure the value matches the scale the user enters + if (!isNull) + { + if (isSqlVal) + { + value = AdjustSqlDecimalScale((SqlDecimal)value, scale); - int maxSupportedSize = Is2008OrNewer ? int.MaxValue : short.MaxValue; + // If Precision is specified, verify value precision vs param precision + if (precision != 0) + { + if (precision < ((SqlDecimal)value).Precision) + { + throw ADP.ParameterValueOutOfRange((SqlDecimal)value); + } + } + } + else + { + value = AdjustDecimalScale((Decimal)value, scale); - if (!isNull) - { - // When writing UDT parameter values to the TDS stream, allow sending byte[] or SqlBytes - // directly to the server and not reject them as invalid. This allows users to handle - // serialization and deserialization logic without having to have SqlClient be aware of - // the types and without using inefficient text representations. - if (value is byte[] rawBytes) - { - udtVal = rawBytes; - } - else if (value is SqlBytes sqlBytes) - { - switch (sqlBytes.Storage) - { - case StorageState.Buffer: - // use the buffer directly, the only way to create it is with the correctly sized byte array - udtVal = sqlBytes.Buffer; - break; - case StorageState.Stream: - case StorageState.UnmanagedBuffer: - // allocate a new byte array to store the data - udtVal = sqlBytes.Value; - break; - } - } - else - { - udtVal = _connHandler.Connection.GetBytes(value, out format, out maxsize); - } + SqlDecimal sqlValue = new SqlDecimal((Decimal)value); - Debug.Assert(udtVal != null, "GetBytes returned null instance. Make sure that it always returns non-null value"); - size = udtVal.Length; + // If Precision is specified, verify value precision vs param precision + if (precision != 0) + { + if (precision < sqlValue.Precision) + { + throw ADP.ParameterValueOutOfRange((Decimal)value); + } + } + } + } + } - //it may be legitimate, but we dont support it yet - if (size < 0 || (size >= maxSupportedSize && maxsize != -1)) - { - throw SQL.UDTInvalidSize(maxsize, maxSupportedSize); - } - } + bool isParameterEncrypted = 0 != (options & TdsEnums.RPC_PARAM_ENCRYPTED); - // Split the input name. TypeName is returned as single 3 part name during DeriveParameters. - // NOTE: ParseUdtTypeName throws if format is incorrect - String[] names = SqlParameter.ParseTypeName(param.UdtTypeName, isUdtTypeName: true); - if (!ADP.IsEmpty(names[0]) && TdsEnums.MAX_SERVERNAME < names[0].Length) - { - throw ADP.ArgumentOutOfRange("names"); - } - if (!ADP.IsEmpty(names[1]) && TdsEnums.MAX_SERVERNAME < names[names.Length - 2].Length) - { - throw ADP.ArgumentOutOfRange("names"); - } - if (TdsEnums.MAX_SERVERNAME < names[2].Length) - { - throw ADP.ArgumentOutOfRange("names"); - } + // Additional information we need to send over wire to the server when writing encrypted parameters. + SqlColumnEncryptionInputParameterInfo encryptedParameterInfoToWrite = null; - WriteUDTMetaData(value, names[0], names[1], names[2], stateObj); + // If the parameter is encrypted, we need to encrypt the value. + if (isParameterEncrypted) + { + Debug.Assert(mt.TDSType != TdsEnums.SQLVARIANT && + mt.TDSType != TdsEnums.SQLUDT && + mt.TDSType != TdsEnums.SQLXMLTYPE && + mt.TDSType != TdsEnums.SQLIMAGE && + mt.TDSType != TdsEnums.SQLTEXT && + mt.TDSType != TdsEnums.SQLNTEXT && + mt.TDSType != TdsEnums.SQLJSON, "Type unsupported for encryption"); - // UNDONE - re-org to use code below to write value! - if (!isNull) - { - WriteUnsignedLong((ulong)udtVal.Length, stateObj); // PLP length - if (udtVal.Length > 0) - { // Only write chunk length if its value is greater than 0 - WriteInt(udtVal.Length, stateObj); // Chunk length - stateObj.WriteByteArray(udtVal, udtVal.Length, 0); // Value - } - WriteInt(0, stateObj); // Terminator - } - else - { - WriteUnsignedLong(TdsEnums.SQL_PLP_NULL, stateObj); // PLP Null. - } - continue; // End of UDT - continue to next parameter. - // UNDONE - need to re-org not to continue at a later point in time. - } - else if (mt.IsPlp) - { - if (mt.SqlDbType != SqlDbType.Xml && mt.SqlDbType != SqlDbTypeExtensions.Json) - WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj); - } - else if ((!mt.IsVarTime) && (mt.SqlDbType != SqlDbType.Date)) - { // Time, Date, DateTime2, DateTimeoffset do not have the size written out - maxsize = (size > actualSize) ? size : actualSize; - if (maxsize == 0 && Is2005OrNewer) - { - // 2005 doesn't like 0 as MaxSize. Change it to 2 for unicode types (SQL9 - 682322) - if (mt.IsNCharType) - maxsize = 2; - else - maxsize = 1; - } + byte[] serializedValue = null; + byte[] encryptedValue = null; - WriteParameterVarLen(mt, maxsize, false/*IsNull*/, stateObj); - } - } + if (!isNull) + { + try + { + if (isSqlVal) + { + serializedValue = SerializeUnencryptedSqlValue(value, mt, actualSize, param.Offset, param.NormalizationRuleVersion, stateObj); + } + else + { + // for codePageEncoded types, WriteValue simply expects the number of characters + // For plp types, we also need the encoded byte size + serializedValue = SerializeUnencryptedValue(value, mt, param.GetActualScale(), actualSize, param.Offset, isDataFeed, param.NormalizationRuleVersion, stateObj); + } - // scale and precision are only relevant for numeric and decimal types - if (mt.SqlDbType == SqlDbType.Decimal) - { - if (0 == precision) - { - if (_is2000) - stateObj.WriteByte(TdsEnums.DEFAULT_NUMERIC_PRECISION); - else - stateObj.WriteByte(TdsEnums.SQL70_DEFAULT_NUMERIC_PRECISION); - } - else - stateObj.WriteByte(precision); + Debug.Assert(serializedValue != null, "serializedValue should not be null in TdsExecuteRPC."); + encryptedValue = SqlSecurityUtility.EncryptWithKey(serializedValue, param.CipherMetadata, _connHandler.Connection, command); + } + catch (Exception e) + { + throw SQL.ParamEncryptionFailed(param.ParameterName, null, e); + } - stateObj.WriteByte(scale); - } - else if (mt.IsVarTime) - { - stateObj.WriteByte(param.GetActualScale()); - } + Debug.Assert(encryptedValue != null && encryptedValue.Length > 0, + "encryptedValue should not be null or empty in TdsExecuteRPC."); + } + else + { + encryptedValue = null; + } - // write out collation or xml metadata + // Change the datatype to varbinary(max). + // Since we don't know the size of the encrypted parameter on the server side, always set to (max). + // + mt = MetaType.MetaMaxVarBinary; + size = -1; + actualSize = (encryptedValue == null) ? 0 : encryptedValue.Length; - if ((mt.SqlDbType == SqlDbType.Xml || mt.SqlDbType == SqlDbTypeExtensions.Json)) - { - if (mt.SqlDbType == SqlDbType.Xml) - { - if (!string.IsNullOrEmpty(param.XmlSchemaCollectionDatabase) || - !string.IsNullOrEmpty(param.XmlSchemaCollectionOwningSchema) || - !string.IsNullOrEmpty(param.XmlSchemaCollectionName)) - { - stateObj.WriteByte(1); //Schema present flag + encryptedParameterInfoToWrite = new SqlColumnEncryptionInputParameterInfo(param.GetMetadataForTypeInfo(), + param.CipherMetadata); - if (!string.IsNullOrEmpty(param.XmlSchemaCollectionDatabase)) - { - tempLen = (param.XmlSchemaCollectionDatabase).Length; - stateObj.WriteByte((byte)(tempLen)); - WriteString(param.XmlSchemaCollectionDatabase, tempLen, 0, stateObj); - } - else - { - stateObj.WriteByte(0); // No dbname - } + // Set the value to the encrypted value and mark isSqlVal as false for VARBINARY encrypted value. + value = encryptedValue; + isSqlVal = false; + } - if (!string.IsNullOrEmpty(param.XmlSchemaCollectionOwningSchema)) - { - tempLen = (param.XmlSchemaCollectionOwningSchema).Length; - stateObj.WriteByte((byte)(tempLen)); - WriteString(param.XmlSchemaCollectionOwningSchema, tempLen, 0, stateObj); - } - else - { - stateObj.WriteByte(0); // no xml schema name - } - if (!string.IsNullOrEmpty(param.XmlSchemaCollectionName)) - { - tempLen = (param.XmlSchemaCollectionName).Length; - WriteShort((short)(tempLen), stateObj); - WriteString(param.XmlSchemaCollectionName, tempLen, 0, stateObj); - } - else - { - WriteShort(0, stateObj); // No xml schema collection name - } + Debug.Assert(isParameterEncrypted == (encryptedParameterInfoToWrite != null), + "encryptedParameterInfoToWrite can be not null if and only if isParameterEncrypted is true."); - } - else - { - stateObj.WriteByte(0); // No schema - } - } - } - else if (mt.IsCharType && mt.SqlDbType != SqlDbTypeExtensions.Json) - { - // if it is not supplied, simply write out our default collation, otherwise, write out the one attached to the parameter - SqlCollation outCollation = (param.Collation != null) ? param.Collation : _defaultCollation; - Debug.Assert(_defaultCollation != null, "_defaultCollation is null!"); + Debug.Assert(!isSqlVal || !isParameterEncrypted, "isParameterEncrypted can be true only if isSqlVal is false."); - WriteUnsignedInt(outCollation._info, stateObj); - stateObj.WriteByte(outCollation._sortId); - } + // fixup the types by using the NullableType property of the MetaType class + // + // following rules should be followed based on feedback from the M-SQL team + // 1) always use the BIG* types (ex: instead of SQLCHAR use SQLBIGCHAR) + // 2) always use nullable types (ex: instead of SQLINT use SQLINTN) + // 3) DECIMALN should always be sent as NUMERICN + // + stateObj.WriteByte(mt.NullableType); - if (0 == codePageByteSize) - WriteParameterVarLen(mt, actualSize, isNull, stateObj, isDataFeed); - else - WriteParameterVarLen(mt, codePageByteSize, isNull, stateObj, isDataFeed); + // handle variants here: the SQLVariant writing routine will write the maxlen and actual len columns + if (mt.TDSType == TdsEnums.SQLVARIANT) + { + // devnote: Do we ever hit this codepath? Yes, when a null value is being written out via a sql variant + // param.GetActualSize is not used + WriteSqlVariantValue(isSqlVal ? MetaType.GetComValueFromSqlVariant(value) : value, param.GetActualSize(), param.Offset, stateObj); + return null; + } - Task writeParamTask = null; - // write the value now - if (!isNull) - { - if (isSqlVal) - { - writeParamTask = WriteSqlValue(value, mt, actualSize, codePageByteSize, param.Offset, stateObj); - } - else - { - // for codePageEncoded types, WriteValue simply expects the number of characters - // For plp types, we also need the encoded byte size - writeParamTask = WriteValue(value, mt, isParameterEncrypted ? (byte)0 : param.GetActualScale(), actualSize, codePageByteSize, isParameterEncrypted ? 0 : param.Offset, stateObj, isParameterEncrypted ? 0 : param.Size, isDataFeed); - } - } + int codePageByteSize = 0; + int maxsize = 0; - // Send encryption metadata for encrypted parameters. - if (isParameterEncrypted) - { - writeParamTask = WriteEncryptionMetadata(writeParamTask, encryptedParameterInfoToWrite, stateObj); - } + if (mt.IsAnsiType) + { + // Avoid the following code block if ANSI but unfilled LazyMat blob + if ((!isNull) && (!isDataFeed)) + { + string s; - if (!sync) - { - if (writeParamTask == null) - { - writeParamTask = stateObj.WaitForAccumulatedWrites(); - } + if (isSqlVal) + { + if (value is SqlString) + { + s = ((SqlString)value).Value; + } + else + { + Debug.Assert(value is SqlChars, "Unknown value for Ansi datatype"); + s = new string(((SqlChars)value).Value); + } + } + else + { + s = (string)value; + } - if (writeParamTask != null) - { - Task task = null; - if (completion == null) - { - completion = new TaskCompletionSource(); - task = completion.Task; - } + codePageByteSize = GetEncodingCharLength(s, actualSize, param.Offset, _defaultEncoding); + } - AsyncHelper.ContinueTaskWithState(writeParamTask, completion, this, - (object state) => - { - TdsParser tdsParser = (TdsParser)state; - TdsExecuteRPC(cmd, rpcArray, timeout, inSchema, notificationRequest, stateObj, isCommandProc, sync, completion, - startRpc: ii, startParam: i + 1); - }, - onFailure: (Exception exc, object state) => ((TdsParser)state).TdsExecuteRPC_OnFailure(exc, stateObj), - connectionToDoom: _connHandler - ); + if (mt.IsPlp) + { + WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj); + } + else + { + maxsize = (size > codePageByteSize) ? size : codePageByteSize; + if (maxsize == 0) + { + // 2005 doesn't like 0 as MaxSize. Change it to 2 for unicode types (SQL9 - 682322) + if (mt.IsNCharType) + maxsize = 2; + else + maxsize = 1; + } - // Take care of releasing the locks - if (releaseConnectionLock) - { - task.ContinueWith( - static (Task _, object state) => ((TdsParser)state)._connHandler._parserLock.Release(), - state: this, - scheduler: TaskScheduler.Default - ); - releaseConnectionLock = false; - } + WriteParameterVarLen(mt, maxsize, false/*IsNull*/, stateObj); + } + } + else + { + // If type timestamp - treat as fixed type and always send over timestamp length, which is 8. + // For fixed types, we either send null or fixed length for type length. We want to match that + // behavior for timestamps. However, in the case of null, we still must send 8 because if we + // send null we will not receive a output val. You can send null for fixed types and still + // receive a output value, but not for variable types. So, always send 8 for timestamp because + // while the user sees it as a fixed type, we are actually representing it as a bigbinary which + // is variable. + if (mt.SqlDbType == SqlDbType.Timestamp) + { + WriteParameterVarLen(mt, TdsEnums.TEXT_TIME_STAMP_LEN, false, stateObj); + } + else if (mt.SqlDbType == SqlDbType.Udt) + { + int maxSupportedSize = Is2008OrNewer ? int.MaxValue : short.MaxValue; + byte[] udtVal = null; + Format format = Format.Native; - return task; - } - } -#if DEBUG - else - { - Debug.Assert(writeParamTask == null, "Should not have a task when executing sync"); - } -#endif - } // parameter for loop + if (string.IsNullOrEmpty(param.UdtTypeName)) + { + throw SQL.MustSetUdtTypeNameForUdtParams(); + } - // If this is not the last RPC we are sending, add the batch flag - if (ii < (rpcArray.Count - 1)) + if (!isNull) + { + // When writing UDT parameter values to the TDS stream, allow sending byte[] or SqlBytes + // directly to the server and not reject them as invalid. This allows users to handle + // serialization and deserialization logic without having to have SqlClient be aware of + // the types and without using inefficient text representations. + if (value is byte[] rawBytes) { - if (_is2005) - { - stateObj.WriteByte(TdsEnums.SQL2005_RPCBATCHFLAG); - - } - else - { - stateObj.WriteByte(TdsEnums.SQL2000_RPCBATCHFLAG); - } + udtVal = rawBytes; + } + else if (value is SqlBytes sqlBytes) + { + switch (sqlBytes.Storage) + { + case StorageState.Buffer: + // use the buffer directly, the only way to create it is with the correctly sized byte array + udtVal = sqlBytes.Buffer; + break; + case StorageState.Stream: + case StorageState.UnmanagedBuffer: + // allocate a new byte array to store the data + udtVal = sqlBytes.Value; + break; + } + } + else + { + udtVal = _connHandler.Connection.GetBytes(value, out format, out maxsize); } - } // rpc for loop - - Task execFlushTask = stateObj.ExecuteFlush(); - Debug.Assert(!sync || execFlushTask == null, "Should not get a task when executing sync"); - if (execFlushTask != null) - { - Task task = null; - if (completion == null) + Debug.Assert(udtVal != null, "GetBytes returned null instance. Make sure that it always returns non-null value"); + size = udtVal.Length; + + if (size < 0 || (size >= maxSupportedSize && maxsize != -1)) { - completion = new TaskCompletionSource(); - task = completion.Task; + throw SQL.UDTInvalidSize(maxsize, maxSupportedSize); } + } - bool taskReleaseConnectionLock = releaseConnectionLock; - execFlushTask.ContinueWith(tsk => ExecuteFlushTaskCallback(tsk, stateObj, completion, taskReleaseConnectionLock), TaskScheduler.Default); + // Split the input name. TypeName is returned as single 3 part name during DeriveParameters. + // NOTE: ParseUdtTypeName throws if format is incorrect + string[] names = SqlParameter.ParseTypeName(param.UdtTypeName, isUdtTypeName: true); + if (!string.IsNullOrEmpty(names[0]) && TdsEnums.MAX_SERVERNAME < names[0].Length) + { + throw ADP.ArgumentOutOfRange(nameof(names)); + } + if (!string.IsNullOrEmpty(names[1]) && TdsEnums.MAX_SERVERNAME < names[names.Length - 2].Length) + { + throw ADP.ArgumentOutOfRange(nameof(names)); + } + if (TdsEnums.MAX_SERVERNAME < names[2].Length) + { + throw ADP.ArgumentOutOfRange(nameof(names)); + } - // ExecuteFlushTaskCallback will take care of the locks for us - releaseConnectionLock = false; + WriteUDTMetaData(value, names[0], names[1], names[2], stateObj); - return task; + // UNDONE - re-org to use code below to write value! + if (!isNull) + { + WriteUnsignedLong((ulong)udtVal.Length, stateObj); // PLP length + if (udtVal.Length > 0) + { // Only write chunk length if its value is greater than 0 + WriteInt(udtVal.Length, stateObj); // Chunk length + stateObj.WriteByteArray(udtVal, udtVal.Length, 0); // Value + } + WriteInt(0, stateObj); // Terminator + } + else + { + WriteUnsignedLong(TdsEnums.SQL_PLP_NULL, stateObj); // PLP Null. } + return null; // End of UDT - continue to next parameter. + // UNDONE - need to re-org not to continue at a later point in time. } - catch (Exception e) + else if (mt.IsPlp) { - FailureCleanup(stateObj, e); - throw; + if (mt.SqlDbType != SqlDbType.Xml && mt.SqlDbType != SqlDbTypeExtensions.Json) + WriteShort(TdsEnums.SQL_USHORTVARMAXLEN, stateObj); } - FinalizeExecuteRPC(stateObj); - if (completion != null) - { - completion.SetResult(null); + else if ((!mt.IsVarTime) && (mt.SqlDbType != SqlDbType.Date)) + { // Time, Date, DateTime2, DateTimeoffset do not have the size written out + maxsize = (size > actualSize) ? size : actualSize; + if (maxsize == 0) + { + // 2005 doesn't like 0 as MaxSize. Change it to 2 for unicode types (SQL9 - 682322) + if (mt.IsNCharType) + maxsize = 2; + else + maxsize = 1; + } + + if (mt.SqlDbType == SqlDbTypeExtensions.Vector) + { + // For vector type we need to write the size in bytes required to represent + // vector value when communicating with SQL Server. + var sqlVectorProps = ((ISqlVector)param.Value); + maxsize = sqlVectorProps.Size; + } + + WriteParameterVarLen(mt, maxsize, false/*IsNull*/, stateObj); } - return null; } - catch (Exception e) + + // scale and precision are only relevant for numeric and decimal types + if (mt.SqlDbType == SqlDbType.Decimal) { - FinalizeExecuteRPC(stateObj); - if (completion != null) + if (0 == precision) { - completion.SetException(e); - return null; + stateObj.WriteByte(TdsEnums.DEFAULT_NUMERIC_PRECISION); } else { - throw; + stateObj.WriteByte(precision); } + + stateObj.WriteByte(scale); } - finally + else if (mt.IsVarTime) { - Debug.Assert(firstCall || !releaseConnectionLock, "Shouldn't be releasing locks synchronously after the first call"); - if (releaseConnectionLock) + stateObj.WriteByte(param.GetActualScale()); + } + // For vector type we need to write scale as the element type of the vector. + else if (mt.SqlDbType == SqlDbTypeExtensions.Vector) + { + stateObj.WriteByte(((ISqlVector)param.Value).ElementType); + } + + // write out collation or xml metadata + + if ((mt.SqlDbType == SqlDbType.Xml || mt.SqlDbType == SqlDbTypeExtensions.Json)) + { + if (mt.SqlDbType == SqlDbType.Xml) { - _connHandler._parserLock.Release(); + if (!string.IsNullOrEmpty(param.XmlSchemaCollectionDatabase) || + !string.IsNullOrEmpty(param.XmlSchemaCollectionOwningSchema) || + !string.IsNullOrEmpty(param.XmlSchemaCollectionName)) + { + stateObj.WriteByte(1); //Schema present flag + + if (!string.IsNullOrEmpty(param.XmlSchemaCollectionDatabase)) + { + tempLen = (param.XmlSchemaCollectionDatabase).Length; + stateObj.WriteByte((byte)(tempLen)); + WriteString(param.XmlSchemaCollectionDatabase, tempLen, 0, stateObj); + } + else + { + stateObj.WriteByte(0); // No dbname + } + + if (!string.IsNullOrEmpty(param.XmlSchemaCollectionOwningSchema)) + { + tempLen = (param.XmlSchemaCollectionOwningSchema).Length; + stateObj.WriteByte((byte)(tempLen)); + WriteString(param.XmlSchemaCollectionOwningSchema, tempLen, 0, stateObj); + } + else + { + stateObj.WriteByte(0); // no xml schema name + } + if (!string.IsNullOrEmpty(param.XmlSchemaCollectionName)) + { + tempLen = (param.XmlSchemaCollectionName).Length; + WriteShort((short)(tempLen), stateObj); + WriteString(param.XmlSchemaCollectionName, tempLen, 0, stateObj); + } + else + { + WriteShort(0, stateObj); // No xml schema collection name + } + + } + else + { + stateObj.WriteByte(0); // No schema + } + } + } + else if (mt.IsCharType && mt.SqlDbType != SqlDbTypeExtensions.Json) + { + // if it is not supplied, simply write out our default collation, otherwise, write out the one attached to the parameter + SqlCollation outCollation = (param.Collation != null) ? param.Collation : _defaultCollation; + Debug.Assert(_defaultCollation != null, "_defaultCollation is null!"); + + WriteUnsignedInt(outCollation._info, stateObj); + stateObj.WriteByte(outCollation._sortId); + } + + if (0 == codePageByteSize) + WriteParameterVarLen(mt, actualSize, isNull, stateObj, isDataFeed); + else + WriteParameterVarLen(mt, codePageByteSize, isNull, stateObj, isDataFeed); + + Task writeParamTask = null; + // write the value now + if (!isNull) + { + if (isSqlVal) + { + writeParamTask = WriteSqlValue(value, mt, actualSize, codePageByteSize, param.Offset, stateObj); + } + else + { + // for codePageEncoded types, WriteValue simply expects the number of characters + // For plp types, we also need the encoded byte size + // For vector type we need to write scale as the element type of the vector. + byte writeScale = mt.SqlDbType == SqlDbTypeExtensions.Vector ? ((ISqlVector)param.Value).ElementType : param.GetActualScale(); + writeParamTask = WriteValue(value, mt, isParameterEncrypted ? (byte)0 : writeScale, actualSize, codePageByteSize, isParameterEncrypted ? 0 : param.Offset, stateObj, isParameterEncrypted ? 0 : param.Size, isDataFeed); } } + + // Send encryption metadata for encrypted parameters. + if (isParameterEncrypted) + { + writeParamTask = WriteEncryptionMetadata(writeParamTask, encryptedParameterInfoToWrite, stateObj); + } + + return writeParamTask; + } + + // 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( + 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), + connectionToDoom: _connHandler + ); + } + + // This is in its own method to avoid always allocating the lambda in TDSExecuteRPCParameter + private void TDSExecuteRPCParameterSetupFlushCompletion(TdsParserStateObject stateObj, TaskCompletionSource completion, Task execFlushTask, bool taskReleaseConnectionLock) + { + execFlushTask.ContinueWith(tsk => ExecuteFlushTaskCallback(tsk, stateObj, completion, taskReleaseConnectionLock), TaskScheduler.Default); } private void WriteEnclaveInfo(TdsParserStateObject stateObj, byte[] enclavePackage) @@ -10803,36 +10368,19 @@ private void TdsExecuteRPC_OnFailure(Exception exc, TdsParserStateObject stateOb RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - FailureCleanup(stateObj, exc); - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG + FailureCleanup(stateObj, exc); } - catch (System.OutOfMemoryException) + catch (OutOfMemoryException) { _connHandler.DoomThisConnection(); throw; } - catch (System.StackOverflowException) + catch (StackOverflowException) { _connHandler.DoomThisConnection(); throw; } - catch (System.Threading.ThreadAbortException) + catch (ThreadAbortException) { _connHandler.DoomThisConnection(); throw; @@ -10847,41 +10395,25 @@ private void ExecuteFlushTaskCallback(Task tsk, TdsParserStateObject stateObj, T if (tsk.Exception != null) { Exception exc = tsk.Exception.InnerException; + RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - FailureCleanup(stateObj, tsk.Exception); - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG + FailureCleanup(stateObj, tsk.Exception); } - catch (System.OutOfMemoryException e) + catch (OutOfMemoryException e) { _connHandler.DoomThisConnection(); completion.SetException(e); throw; } - catch (System.StackOverflowException e) + catch (StackOverflowException e) { _connHandler.DoomThisConnection(); completion.SetException(e); throw; } - catch (System.Threading.ThreadAbortException e) + catch (ThreadAbortException e) { _connHandler.DoomThisConnection(); completion.SetException(e); @@ -10942,7 +10474,6 @@ private void WriteParameterName(string rawParameterName, TdsParserStateObject st } } - private static readonly IEnumerable __tvpEmptyValue = new List().AsReadOnly(); private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefault, TdsParserStateObject stateObj, bool isAnonymous, bool advancedTraceIsOn) { // @@ -10969,7 +10500,7 @@ private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefa // Value for TVP default is empty list, not NULL if (SqlDbType.Structured == metaData.SqlDbType && metaData.IsMultiValued) { - value = __tvpEmptyValue; + value = Array.Empty(); typeCode = ExtendedClrTypeCode.IEnumerableOfSqlDataRecord; } else @@ -10991,7 +10522,10 @@ private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefa { value = param.GetCoercedValue(); typeCode = MetaDataUtilsSmi.DetermineExtendedTypeCodeForUseWithSqlDbType( - metaData.SqlDbType, metaData.IsMultiValued, value, null, SmiContextFactory.Sql2008Version); + metaData.SqlDbType, + metaData.IsMultiValued, + value, + udtType: null); } var sendDefaultValue = sendDefault ? 1 : 0; @@ -11010,7 +10544,6 @@ private void WriteSmiParameter(SqlParameter param, int paramIndex, bool sendDefa // TdsParameterSetter paramSetter = new TdsParameterSetter(stateObj, metaData); ValueUtilsSmi.SetCompatibleValueV200( - new SmiEventSink_Default(), // TDS Errors/events dealt with at lower level for now, just need an object for processing paramSetter, 0, // ordinal. TdsParameterSetter only handles one parameter at a time metaData, @@ -11373,7 +10906,7 @@ internal Task WriteBulkCopyDone(TdsParserStateObject stateObj) /// internal void LoadColumnEncryptionKeys(_SqlMetaDataSet metadataCollection, SqlConnection connection, SqlCommand command = null) { - if (_serverSupportsColumnEncryption && ShouldEncryptValuesForBulkCopy()) + if (IsColumnEncryptionSupported && ShouldEncryptValuesForBulkCopy()) { for (int col = 0; col < metadataCollection.Length; col++) { @@ -11421,7 +10954,7 @@ internal void WriteEncryptionEntries(ref SqlTceCipherInfoTable cekTable, TdsPars /// internal void WriteCekTable(_SqlMetaDataSet metadataCollection, TdsParserStateObject stateObj) { - if (!_serverSupportsColumnEncryption) + if (!IsColumnEncryptionSupported) { return; } @@ -11476,7 +11009,7 @@ internal void WriteTceUserTypeAndTypeInfo(SqlMetaDataPriv mdPriv, TdsParserState break; default: WriteTokenLength(mdPriv.tdsType, mdPriv.length, stateObj); - if (mdPriv.metaType.IsCharType && _is2000) + if (mdPriv.metaType.IsCharType) { WriteUnsignedInt(mdPriv.collation._info, stateObj); stateObj.WriteByte(mdPriv.collation._sortId); @@ -11491,7 +11024,7 @@ internal void WriteTceUserTypeAndTypeInfo(SqlMetaDataPriv mdPriv, TdsParserState /// internal void WriteCryptoMetadata(_SqlMetaData md, TdsParserStateObject stateObj) { - if (!_serverSupportsColumnEncryption || // TCE Feature supported + if (!IsColumnEncryptionSupported || // TCE Feature supported !md.isEncrypted || // Column is not encrypted !ShouldEncryptValuesForBulkCopy()) { // TCE disabled on connection string @@ -11542,23 +11075,16 @@ internal void WriteBulkCopyMetaData(_SqlMetaDataSet metadataCollection, int coun _SqlMetaData md = metadataCollection[i]; // read user type - 4 bytes 2005, 2 backwards - if (Is2005OrNewer) - { WriteInt(0x0, stateObj); - } - else - { - WriteShort(0x0000, stateObj); - } // Write the flags - UInt16 flags; - flags = (UInt16)(md.Updatability << 2); - flags |= (UInt16)(md.IsNullable ? (UInt16)TdsEnums.Nullable : (UInt16)0); - flags |= (UInt16)(md.IsIdentity ? (UInt16)TdsEnums.Identity : (UInt16)0); + ushort flags; + flags = (ushort)(md.Updatability << 2); + flags |= md.IsNullable ? TdsEnums.Nullable : (ushort)0; + flags |= md.IsIdentity ? TdsEnums.Identity : (ushort)0; // Write the next byte of flags - if (_serverSupportsColumnEncryption) + if (IsColumnEncryptionSupported) { // TCE Supported if (ShouldEncryptValuesForBulkCopy()) { // TCE enabled on connection options @@ -11566,7 +11092,7 @@ internal void WriteBulkCopyMetaData(_SqlMetaDataSet metadataCollection, int coun } } - WriteShort(flags, stateObj);// write the flags + WriteShort(flags, stateObj); // write the flags // todo: // for xml WriteTokenLength results in a no-op @@ -11601,10 +11127,15 @@ internal void WriteBulkCopyMetaData(_SqlMetaDataSet metadataCollection, int coun case SqlDbTypeExtensions.Json: stateObj.WriteByteArray(s_jsonMetadataSubstituteSequence, s_xmlMetadataSubstituteSequence.Length, 0); break; + case SqlDbTypeExtensions.Vector: + stateObj.WriteByte(md.tdsType); + WriteTokenLength(md.tdsType, md.length, stateObj); + stateObj.WriteByte(md.scale); + break; default: stateObj.WriteByte(md.tdsType); WriteTokenLength(md.tdsType, md.length, stateObj); - if (md.metaType.IsCharType && _is2000) + if (md.metaType.IsCharType) { WriteUnsignedInt(md.collation._info, stateObj); stateObj.WriteByte(md.collation._sortId); @@ -11648,7 +11179,7 @@ internal bool ShouldEncryptValuesForBulkCopy() /// internal object EncryptColumnValue(object value, SqlMetaDataPriv metadata, string column, TdsParserStateObject stateObj, bool isDataFeed, bool isSqlType) { - Debug.Assert(_serverSupportsColumnEncryption, "Server doesn't support encryption, yet we received encryption metadata"); + Debug.Assert(IsColumnEncryptionSupported, "Server doesn't support encryption, yet we received encryption metadata"); Debug.Assert(ShouldEncryptValuesForBulkCopy(), "Encryption attempted when not requested"); if (isDataFeed) @@ -11674,7 +11205,7 @@ internal object EncryptColumnValue(object value, SqlMetaDataPriv metadata, strin actualLengthInBytes = (isSqlType) ? ((SqlBinary)value).Length : ((byte[])value).Length; if (metadata.baseTI.length > 0 && actualLengthInBytes > metadata.baseTI.length) - { + { // see comments above actualLengthInBytes = metadata.baseTI.length; } @@ -11778,7 +11309,7 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars // Replace encoding if it is UTF8 if (metadata.collation.IsUTF8) { - _defaultEncoding = Encoding.UTF8; + _defaultEncoding = s_utf8EncodingWithoutBom; } _defaultCollation = metadata.collation; @@ -11816,6 +11347,7 @@ internal Task WriteBulkCopyValue(object value, SqlMetaDataPriv metadata, TdsPars case TdsEnums.SQLBIGVARBINARY: case TdsEnums.SQLIMAGE: case TdsEnums.SQLUDT: + case TdsEnums.SQLVECTOR: ccb = (isSqlType) ? ((SqlBinary)value).Length : ((byte[])value).Length; break; case TdsEnums.SQLUNIQUEID: @@ -11964,9 +11496,6 @@ private Task WriteBulkCopyValueSetupContinuation(Task internalWriteTask, Encodin // Write mars header data, not including the mars header length private void WriteMarsHeaderData(TdsParserStateObject stateObj, SqlInternalTransaction transaction) { - // Function to send over additional payload header data for 2005 and beyond only. - Debug.Assert(_is2005, "WriteMarsHeaderData called on a non-2005 server"); - // We may need to update the mars header length if mars header is changed in the future WriteShort(TdsEnums.HEADERTYPE_MARS, stateObj); @@ -11993,25 +11522,25 @@ private int GetNotificationHeaderSize(SqlNotificationRequest notificationRequest if (callbackId == null) { - throw ADP.ArgumentNull("CallbackId"); + throw ADP.ArgumentNull(nameof(callbackId)); } - else if (UInt16.MaxValue < callbackId.Length) + else if (ushort.MaxValue < callbackId.Length) { - throw ADP.ArgumentOutOfRange("CallbackId"); + throw ADP.ArgumentOutOfRange(nameof(callbackId)); } if (service == null) { - throw ADP.ArgumentNull("Service"); + throw ADP.ArgumentNull(nameof(service)); } - else if (UInt16.MaxValue < service.Length) + else if (ushort.MaxValue < service.Length) { - throw ADP.ArgumentOutOfRange("Service"); + throw ADP.ArgumentOutOfRange(nameof(service)); } if (-1 > timeout) { - throw ADP.ArgumentOutOfRange("Timeout"); + throw ADP.ArgumentOutOfRange(nameof(timeout)); } // Header Length (uint) (included in size) (already written to output buffer) @@ -12037,8 +11566,6 @@ private int GetNotificationHeaderSize(SqlNotificationRequest notificationRequest // Write query notificaiton header data, not including the notificaiton header length private void WriteQueryNotificationHeaderData(SqlNotificationRequest notificationRequest, TdsParserStateObject stateObj) { - Debug.Assert(_is2005, "WriteQueryNotificationHeaderData called on a non-2005 server"); - // We may need to update the notification header length if the header is changed in the future Debug.Assert(notificationRequest != null, "notificationRequest is null"); @@ -12049,9 +11576,9 @@ private void WriteQueryNotificationHeaderData(SqlNotificationRequest notificatio // we did verification in GetNotificationHeaderSize, so just assert here. Debug.Assert(callbackId != null, "CallbackId is null"); - Debug.Assert(UInt16.MaxValue >= callbackId.Length, "CallbackId length is out of range"); + Debug.Assert(ushort.MaxValue >= callbackId.Length, "CallbackId length is out of range"); Debug.Assert(service != null, "Service is null"); - Debug.Assert(UInt16.MaxValue >= service.Length, "Service length is out of range"); + Debug.Assert(ushort.MaxValue >= service.Length, "Service length is out of range"); Debug.Assert(-1 <= timeout, "Timeout"); SqlClientEventSource.Log.TryNotificationTraceEvent(" NotificationRequest: userData: '{0}', options: '{1}', timeout: '{2}'", notificationRequest.UserData, notificationRequest.Options, notificationRequest.Timeout); @@ -12085,8 +11612,6 @@ private void WriteTraceHeaderData(TdsParserStateObject stateObj) private void WriteRPCBatchHeaders(TdsParserStateObject stateObj, SqlNotificationRequest notificationRequest) { - Debug.Assert(_is2005, "WriteRPCBatchHeaders can only be called on 2005 or higher version servers"); - /* Header: TotalLength - DWORD - including all headers and lengths, including itself Each Data Session: @@ -12099,39 +11624,37 @@ private void WriteRPCBatchHeaders(TdsParserStateObject stateObj, SqlNotification int notificationHeaderSize = GetNotificationHeaderSize(notificationRequest); - const int marsHeaderSize = 18; // 4 + 2 + 8 + 4 + const int MarsHeaderSize = 18; // 4 + 2 + 8 + 4 // Header Length (DWORD) // Header Type (ushort) // Trace Data Guid // Trace Data Sequence Number (uint) - const int traceHeaderSize = 26; // 4 + 2 + GUID_SIZE + sizeof(UInt32); + const int TraceHeaderSize = 26; // 4 + 2 + sizeof(Guid) + sizeof(uint); // TotalLength - DWORD - including all headers and lengths, including itself - int totalHeaderLength = this.IncludeTraceHeader ? (4 + marsHeaderSize + notificationHeaderSize + traceHeaderSize) : (4 + marsHeaderSize + notificationHeaderSize); + int totalHeaderLength = IncludeTraceHeader ? (4 + MarsHeaderSize + notificationHeaderSize + TraceHeaderSize) : (4 + MarsHeaderSize + notificationHeaderSize); Debug.Assert(stateObj._outBytesUsed == stateObj._outputHeaderLen, "Output bytes written before total header length"); // Write total header length WriteInt(totalHeaderLength, stateObj); - // Write Mars header length - WriteInt(marsHeaderSize, stateObj); - // Write Mars header data + // Write MARS header length and data + WriteInt(MarsHeaderSize, stateObj); WriteMarsHeaderData(stateObj, CurrentTransaction); - if (0 != notificationHeaderSize) + if (notificationHeaderSize != 0) { - // Write Notification header length + // Write notification header length and data + // MS-TDS 2.2.5.3.1: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/e168d373-a7b7-41aa-b6ca-25985466a7e0 WriteInt(notificationHeaderSize, stateObj); - // Write notificaiton header data WriteQueryNotificationHeaderData(notificationRequest, stateObj); } if (IncludeTraceHeader) { - - // Write trace header length - WriteInt(traceHeaderSize, stateObj); - // Write trace header data + // Write trace header length and data + // MS-TDS 2.2.5.3.3: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tds/6e9f106b-df6e-4cbe-a6eb-45ceb10c63be + WriteInt(TraceHeaderSize, stateObj); WriteTraceHeaderData(stateObj); } } @@ -12149,16 +11672,19 @@ private void WriteTokenLength(byte token, int length, TdsParserStateObject state // For Plp fields, this should only be used when writing to metadata header. // For actual data length, WriteDataLength should be used. // For Xml fields, there is no token length field. For MAX fields it is 0xffff. - if (_is2005) - { // Handle 2005 specific exceptions - if (TdsEnums.SQLUDT == token) - { - tokenLength = 8; - } - else if (token == TdsEnums.SQLXMLTYPE) - { - tokenLength = 8; - } + if (TdsEnums.SQLUDT == token) + { + tokenLength = 8; + } + else if (token == TdsEnums.SQLXMLTYPE) + { + tokenLength = 8; + } + else if (token == TdsEnums.SQLVECTOR) + { + tokenLength = 2; + WriteShort(length, stateObj); + return; } if (tokenLength == 0) @@ -12230,9 +11756,9 @@ private bool IsBOMNeeded(MetaType type, object value) return true; } } - else if ((currentType == typeof(String)) && (((String)value).Length > 0)) + else if ((currentType == typeof(string)) && (((String)value).Length > 0)) { - if ((value != null) && (((String)value)[0] & 0xff) != 0xff) + if ((value != null) && (((string)value)[0] & 0xff) != 0xff) return true; } else if (currentType == typeof(SqlXml)) @@ -12375,7 +11901,7 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe } if (value is SqlChars) { - String sch = new String(((SqlChars)value).Value); + string sch = new string(((SqlChars)value).Value); return WriteEncodingChar(sch, actualLength, offset, _defaultEncoding, stateObj, canAccumulate: false); } @@ -12437,7 +11963,7 @@ private Task WriteUnterminatedSqlValue(object value, MetaType type, int actualLe if (type.FixedLength == 4) { - if (0 > dt.DayTicks || dt.DayTicks > UInt16.MaxValue) + if (0 > dt.DayTicks || dt.DayTicks > ushort.MaxValue) throw SQL.SmallDateTimeOverflow(dt.ToString()); WriteShort(dt.DayTicks, stateObj); @@ -12577,49 +12103,26 @@ public override Task WriteAsync(byte[] buffer, int offset, int count, Cancellati RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try + Task task = null; + if (count > 0) { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - Task task = null; - if (count > 0) - { - _parser.WriteInt(count, _stateObj); // write length of chunk - task = _stateObj.WriteByteArray(buffer, count, offset, canAccumulate: false); - } - if (task == null) - { - return CompletedTask; - } - else - { - return task; - } - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); + _parser.WriteInt(count, _stateObj); // write length of chunk + task = _stateObj.WriteByteArray(buffer, count, offset, canAccumulate: false); } -#endif //DEBUG + + return task ?? Task.CompletedTask; } - catch (System.OutOfMemoryException) + catch (OutOfMemoryException) { _parser._connHandler.DoomThisConnection(); throw; } - catch (System.StackOverflowException) + catch (StackOverflowException) { _parser._connHandler.DoomThisConnection(); throw; } - catch (System.Threading.ThreadAbortException) + catch (ThreadAbortException) { _parser._connHandler.DoomThisConnection(); throw; @@ -12708,7 +12211,6 @@ public override void Write(char value) public override void Write(char[] buffer, int index, int count) { - ValidateWriteParameters(buffer, index, count); Debug.Assert(_size >= _written); @@ -12728,7 +12230,7 @@ public override Task WriteAsync(char value) return _next.WriteAsync(value); } - return CompletedTask; + return Task.CompletedTask; } public override Task WriteAsync(char[] buffer, int index, int count) @@ -12743,7 +12245,7 @@ public override Task WriteAsync(char[] buffer, int index, int count) return _next.WriteAsync(buffer, index, count); } - return CompletedTask; + return Task.CompletedTask; } public override Task WriteAsync(string value) @@ -12790,7 +12292,7 @@ private async Task WriteXmlFeed(XmlDataFeed feed, TdsParserStateObject stateObj, ConstrainedTextWriter writer = new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, preambleToSkip), encoding), size); XmlWriterSettings writerSettings = new XmlWriterSettings(); - writerSettings.CloseOutput = false; // don't close the memory stream + writerSettings.CloseOutput = false; // don't close the memory stream writerSettings.ConformanceLevel = ConformanceLevel.Fragment; if (_asyncWrite) { @@ -12837,119 +12339,127 @@ private async Task WriteXmlFeed(XmlDataFeed feed, TdsParserStateObject stateObj, private async Task WriteTextFeed(TextDataFeed feed, Encoding encoding, bool needBom, TdsParserStateObject stateObj, int size, bool useReadBlock) { Debug.Assert(encoding == null || !needBom); - char[] inBuff = new char[constTextBufferSize]; + char[] inBuff = ArrayPool.Shared.Rent(constTextBufferSize); encoding = encoding ?? new UnicodeEncoding(false, false); - ConstrainedTextWriter writer = new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, null), encoding), size); - if (needBom) + using (ConstrainedTextWriter writer = new ConstrainedTextWriter(new StreamWriter(new TdsOutputStream(this, stateObj, null), encoding), size)) { - if (_asyncWrite) - { - await writer.WriteAsync((char)TdsEnums.XMLUNICODEBOM).ConfigureAwait(false); - } - else + if (needBom) { - writer.Write((char)TdsEnums.XMLUNICODEBOM); + if (_asyncWrite) + { + await writer.WriteAsync((char)TdsEnums.XMLUNICODEBOM).ConfigureAwait(false); + } + else + { + writer.Write((char)TdsEnums.XMLUNICODEBOM); + } } - } - - int nWritten = 0; - do - { - int nRead = 0; - if (_asyncWrite) + int nWritten = 0; + do { - if (useReadBlock) + int nRead = 0; + + if (_asyncWrite) { - nRead = await feed._source.ReadBlockAsync(inBuff, 0, constTextBufferSize).ConfigureAwait(false); + if (useReadBlock) + { + nRead = await feed._source.ReadBlockAsync(inBuff, 0, constTextBufferSize).ConfigureAwait(false); + } + else + { + nRead = await feed._source.ReadAsync(inBuff, 0, constTextBufferSize).ConfigureAwait(false); + } } else { - nRead = await feed._source.ReadAsync(inBuff, 0, constTextBufferSize).ConfigureAwait(false); + if (useReadBlock) + { + nRead = feed._source.ReadBlock(inBuff, 0, constTextBufferSize); + } + else + { + nRead = feed._source.Read(inBuff, 0, constTextBufferSize); + } + } + + if (nRead == 0) + { + break; } - } - else - { - if (useReadBlock) + + if (_asyncWrite) { - nRead = feed._source.ReadBlock(inBuff, 0, constTextBufferSize); + await writer.WriteAsync(inBuff, 0, nRead).ConfigureAwait(false); } else { - nRead = feed._source.Read(inBuff, 0, constTextBufferSize); + writer.Write(inBuff, 0, nRead); } - } - if (nRead == 0) - { - break; - } + nWritten += nRead; + } while (!writer.IsComplete); if (_asyncWrite) { - await writer.WriteAsync(inBuff, 0, nRead).ConfigureAwait(false); + await writer.FlushAsync().ConfigureAwait(false); } else { - writer.Write(inBuff, 0, nRead); + writer.Flush(); } - - nWritten += nRead; - } while (!writer.IsComplete); - - if (_asyncWrite) - { - await writer.FlushAsync().ConfigureAwait(false); - } - else - { - writer.Flush(); } + ArrayPool.Shared.Return(inBuff, clearArray: true); } private async Task WriteStreamFeed(StreamDataFeed feed, TdsParserStateObject stateObj, int len) { - TdsOutputStream output = new TdsOutputStream(this, stateObj, null); - byte[] buff = new byte[constBinBufferSize]; - int nWritten = 0; - do + byte[] buff = ArrayPool.Shared.Rent(constBinBufferSize); + + using (TdsOutputStream output = new TdsOutputStream(this, stateObj, null)) { - int nRead = 0; - int readSize = constBinBufferSize; - if (len > 0 && nWritten + readSize > len) + int nWritten = 0; + do { - readSize = len - nWritten; - } + int nRead = 0; + int readSize = constBinBufferSize; + if (len > 0 && nWritten + readSize > len) + { + readSize = len - nWritten; + } - Debug.Assert(readSize >= 0); + Debug.Assert(readSize >= 0); - if (_asyncWrite) - { - nRead = await feed._source.ReadAsync(buff, 0, readSize).ConfigureAwait(false); - } - else - { - nRead = feed._source.Read(buff, 0, readSize); - } + if (_asyncWrite) + { + nRead = await feed._source.ReadAsync(buff, 0, readSize).ConfigureAwait(false); + } + else + { + nRead = feed._source.Read(buff, 0, readSize); + } - if (nRead == 0) - { - return; - } + if (nRead == 0) + { + return; + } - if (_asyncWrite) - { - await output.WriteAsync(buff, 0, nRead).ConfigureAwait(false); - } - else - { - output.Write(buff, 0, nRead); - } + if (_asyncWrite) + { + await output.WriteAsync(buff, 0, nRead).ConfigureAwait(false); + } + else + { + output.Write(buff, 0, nRead); + } + + nWritten += nRead; + } while (len <= 0 || nWritten < len); + } - nWritten += nRead; - } while (len <= 0 || nWritten < len); + ArrayPool.Shared.Return(buff, clearArray: true); } private Task NullIfCompletedWriteTask(Task task) @@ -12988,11 +12498,11 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int { case TdsEnums.SQLFLTN: if (type.FixedLength == 4) - WriteFloat((Single)value, stateObj); + WriteFloat((float)value, stateObj); else { Debug.Assert(type.FixedLength == 8, "Invalid length for SqlDouble type!"); - WriteDouble((Double)value, stateObj); + WriteDouble((double)value, stateObj); } break; @@ -13001,6 +12511,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int case TdsEnums.SQLBIGVARBINARY: case TdsEnums.SQLIMAGE: case TdsEnums.SQLUDT: + case TdsEnums.SQLVECTOR: { // An array should be in the object Debug.Assert(isDataFeed || value is byte[], "Value should be an array of bytes"); @@ -13017,7 +12528,6 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int { WriteInt(actualLength, stateObj); // chunk length } - return stateObj.WriteByteArray((byte[])value, actualLength, offset, canAccumulate: false); } } @@ -13047,13 +12557,13 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int if (type.FixedLength == 1) stateObj.WriteByte((byte)value); else if (type.FixedLength == 2) - WriteShort((Int16)value, stateObj); + WriteShort((short)value, stateObj); else if (type.FixedLength == 4) - WriteInt((Int32)value, stateObj); + WriteInt((int)value, stateObj); else { Debug.Assert(type.FixedLength == 8, "invalid length for SqlIntN type: " + type.FixedLength.ToString(CultureInfo.InvariantCulture)); - WriteLong((Int64)value, stateObj); + WriteLong((long)value, stateObj); } break; @@ -13153,7 +12663,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int } case TdsEnums.SQLNUMERICN: Debug.Assert(type.FixedLength <= 17, "Decimal length cannot be greater than 17 bytes"); - WriteDecimal((Decimal)value, stateObj); + WriteDecimal((decimal)value, stateObj); break; case TdsEnums.SQLDATETIMN: @@ -13163,7 +12673,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int if (type.FixedLength == 4) { - if (0 > dt.days || dt.days > UInt16.MaxValue) + if (0 > dt.days || dt.days > ushort.MaxValue) throw SQL.SmallDateTimeOverflow(MetaType.ToDateTime(dt.days, dt.time, 4).ToString(CultureInfo.InvariantCulture)); WriteShort(dt.days, stateObj); @@ -13179,7 +12689,7 @@ private Task WriteUnterminatedValue(object value, MetaType type, byte scale, int case TdsEnums.SQLMONEYN: { - WriteCurrency((Decimal)value, type.FixedLength, stateObj); + WriteCurrency((decimal)value, type.FixedLength, stateObj); break; } @@ -13738,7 +13248,12 @@ internal int ReadPlpUnicodeChars(ref char[] buff, int offst, int len, TdsParserS // requested length is -1 or larger than the actual length of data. First call to this method // should be preceeded by a call to ReadPlpLength or ReadDataLength. // Returns the actual chars read. - internal TdsOperationStatus TryReadPlpUnicodeChars(ref char[] buff, int offst, int len, TdsParserStateObject stateObj, out int totalCharsRead) + internal TdsOperationStatus TryReadPlpUnicodeChars( + ref char[] buff, + int offst, + int len, + TdsParserStateObject stateObj, + out int totalCharsRead) { int charsRead = 0; int charsLeft = 0; @@ -13835,7 +13350,7 @@ internal TdsOperationStatus TryReadPlpUnicodeChars(ref char[] buff, int offst, i totalCharsRead++; } if (stateObj._longlenleft == 0) - { + { // Read the next chunk or cleanup state if hit the end result = stateObj.TryReadPlpLength(false, out _); if (result != TdsOperationStatus.Done) @@ -13954,8 +13469,8 @@ internal TdsOperationStatus TrySkipPlpValue(ulong cb, TdsParserStateObject state while ((totalBytesSkipped < cb) && (stateObj._longlenleft > 0)) { - if (stateObj._longlenleft > Int32.MaxValue) - bytesSkipped = Int32.MaxValue; + if (stateObj._longlenleft > int.MaxValue) + bytesSkipped = int.MaxValue; else bytesSkipped = (int)stateObj._longlenleft; bytesSkipped = ((cb - totalBytesSkipped) < (ulong)bytesSkipped) ? (int)(cb - totalBytesSkipped) : bytesSkipped; @@ -14034,18 +13549,14 @@ internal ulong PlpBytesTotalLength(TdsParserStateObject stateObj) + " _connHandler = {14}\n\t" + " _fMARS = {15}\n\t" + " _sessionPool = {16}\n\t" - + " _is2000 = {17}\n\t" - + " _is2000SP1 = {18}\n\t" - + " _is2005 = {19}\n\t" - + " _sniSpnBuffer = {20}\n\t" - + " _errors = {21}\n\t" - + " _warnings = {22}\n\t" - + " _attentionErrors = {23}\n\t" - + " _attentionWarnings = {24}\n\t" - + " _statistics = {25}\n\t" - + " _statisticsIsInTransaction = {26}\n\t" - + " _fPreserveTransaction = {27}" - + " _fParallel = {28}" + + " _errors = {17}\n\t" + + " _warnings = {18}\n\t" + + " _attentionErrors = {19}\n\t" + + " _attentionWarnings = {20}\n\t" + + " _statistics = {21}\n\t" + + " _statisticsIsInTransaction = {22}\n\t" + + " _fPreserveTransaction = {23}" + + " _fParallel = {24}" ; internal string TraceString() { @@ -14068,10 +13579,6 @@ internal string TraceString() _connHandler == null ? "(null)" : _connHandler.ObjectID.ToString((IFormatProvider)null), _fMARS ? bool.TrueString : bool.FalseString, _sessionPool == null ? "(null)" : _sessionPool.TraceString(), - _is2000 ? bool.TrueString : bool.FalseString, - _is2000SP1 ? bool.TrueString : bool.FalseString, - _is2005 ? bool.TrueString : bool.FalseString, - _sniSpnBuffer == null ? "(null)" : _sniSpnBuffer.Length.ToString((IFormatProvider)null), _physicalStateObj != null ? "(null)" : _physicalStateObj.ErrorCount.ToString((IFormatProvider)null), _physicalStateObj != null ? "(null)" : _physicalStateObj.WarningCount.ToString((IFormatProvider)null), _physicalStateObj != null ? "(null)" : _physicalStateObj.PreAttentionErrorCount.ToString((IFormatProvider)null), diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.netfx.cs new file mode 100644 index 0000000000..6841904b33 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParser.netfx.cs @@ -0,0 +1,126 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System; +using System.Net; +using System.Threading; +using Interop.Windows.Sni; + +namespace Microsoft.Data.SqlClient +{ + internal sealed partial class TdsParser + { + // This is called from a ThreadAbort - ensure that it can be run from a CER Catch + internal void BestEffortCleanup() + { + _state = TdsParserState.Broken; + + var stateObj = _physicalStateObj; + if (stateObj != null) + { + var stateObjHandle = stateObj.Handle; + if (stateObjHandle != null) + { + stateObjHandle.Dispose(); + } + } + + if (_fMARS) + { + var sessionPool = _sessionPool; + if (sessionPool != null) + { + sessionPool.BestEffortCleanup(); + } + + var marsStateObj = _pMarsPhysicalConObj; + if (marsStateObj != null) + { + var marsStateObjHandle = marsStateObj.Handle; + if (marsStateObjHandle != null) + { + marsStateObjHandle.Dispose(); + } + } + } + } + + // Retrieve the IP and port number from native SNI for TCP protocol. The IP information is stored temporarily in the + // pendingSQLDNSObject but not in the DNS Cache at this point. We only add items to the DNS Cache after we receive the + // IsSupported flag as true in the feature ext ack from server. + internal void AssignPendingDNSInfo(string userProtocol, string DNSCacheKey) + { + uint result; + ushort portFromSNI = 0; + string IPStringFromSNI = string.Empty; + IPAddress IPFromSNI; + isTcpProtocol = false; + Provider providerNumber = Provider.INVALID_PROV; + + if (string.IsNullOrEmpty(userProtocol)) + { + + result = SniNativeWrapper.SniGetProviderNumber(_physicalStateObj.Handle, ref providerNumber); + Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetProviderNumber"); + isTcpProtocol = (providerNumber == Provider.TCP_PROV); + } + else if (userProtocol == TdsEnums.TCP) + { + isTcpProtocol = true; + } + + // serverInfo.UserProtocol could be empty + if (isTcpProtocol) + { + result = SniNativeWrapper.SniGetConnectionPort(_physicalStateObj.Handle, ref portFromSNI); + Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionPort"); + + + result = SniNativeWrapper.SniGetConnectionIpString(_physicalStateObj.Handle, ref IPStringFromSNI); + Debug.Assert(result == TdsEnums.SNI_SUCCESS, "Unexpected failure state upon calling SniGetConnectionIPString"); + + _connHandler.pendingSQLDNSObject = new SQLDNSInfo(DNSCacheKey, null, null, portFromSNI.ToString()); + + if (IPAddress.TryParse(IPStringFromSNI, out IPFromSNI)) + { + if (System.Net.Sockets.AddressFamily.InterNetwork == IPFromSNI.AddressFamily) + { + _connHandler.pendingSQLDNSObject.AddrIPv4 = IPStringFromSNI; + } + else if (System.Net.Sockets.AddressFamily.InterNetworkV6 == IPFromSNI.AddressFamily) + { + _connHandler.pendingSQLDNSObject.AddrIPv6 = IPStringFromSNI; + } + } + } + else + { + _connHandler.pendingSQLDNSObject = null; + } + } + + // @TODO: Consider adopting this pattern for all usages of Run and rename to Run. + internal bool RunReliably(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) + { + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + return Run(runBehavior, cmdHandler, dataStream, bulkCopyHandler, stateObj); + } + catch (OutOfMemoryException) + { + _connHandler.DoomThisConnection(); + throw; + } + catch (StackOverflowException) + { + _connHandler.DoomThisConnection(); + throw; + } + catch (ThreadAbortException) + { + _connHandler.DoomThisConnection(); + throw; + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs index 0544d38766..a64afdce80 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObject.netfx.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers.Binary; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; @@ -17,44 +18,17 @@ namespace Microsoft.Data.SqlClient { - using PacketHandle = IntPtr; - - internal readonly ref struct SessionHandle - { - public readonly SNIHandle NativeHandle; - - public SessionHandle(SNIHandle nativeHandle) => NativeHandle = nativeHandle; - - public bool IsNull => NativeHandle is null; - } - internal partial class TdsParserStateObject { - private static class TdsParserStateObjectFactory - { - /// - /// Always false in case of netfx. Only needed for merging with netcore codebase. - /// - internal const bool UseManagedSNI = false; - } - - private SNIHandle _sessionHandle = null; // the SNI handle we're to work on + protected SNIHandle _sessionHandle = null; // the SNI handle we're to work on // SNI variables // multiple resultsets in one batch. - private SNIPacket _sniPacket = null; // Will have to re-vamp this for MARS + protected SNIPacket _sniPacket = null; // Will have to re-vamp this for MARS internal SNIPacket _sniAsyncAttnPacket = null; // Packet to use to send Attn - private readonly WritePacketCache _writePacketCache = new WritePacketCache(); // Store write packets that are ready to be re-used - private readonly Dictionary _pendingWritePackets = new Dictionary(); // Stores write packets that have been sent to SNI, but have not yet finished writing (i.e. we are waiting for SNI's callback) // Async variables private GCHandle _gcHandle; // keeps this object alive until we're closed. - // This variable is used to prevent sending an attention by another thread that is not the - // current owner of the stateObj. I currently do not know how this can happen. Mark added - // the code but does not remember either. At some point, we need to research killing this - // logic. - private volatile int _allowObjectID; - // Used for blanking out password in trace. internal int _tracePasswordOffset = 0; internal int _tracePasswordLength = 0; @@ -65,7 +39,7 @@ private static class TdsParserStateObjectFactory // Constructors // ////////////////// - internal TdsParserStateObject(TdsParser parser, SNIHandle physicalConnection, bool async) + protected TdsParserStateObject(TdsParser parser, SNIHandle physicalConnection, bool async) { // Construct a MARS session Debug.Assert(parser != null, "no parser?"); @@ -88,7 +62,7 @@ internal TdsParserStateObject(TdsParser parser, SNIHandle physicalConnection, bo SQLFallbackDNSCache.Instance.GetDNSInfo(_parser.FQDNforDNSCache, out cachedDNSInfo); _sessionHandle = new SNIHandle(myInfo, physicalConnection, _parser.Connection.ConnectionOptions.IPAddressPreference, cachedDNSInfo); - if (_sessionHandle.Status != TdsEnums.SNI_SUCCESS) + if (IsFailedHandle()) { AddError(parser.ProcessSNIError(this)); ThrowExceptionAndWarning(); @@ -101,8 +75,6 @@ internal TdsParserStateObject(TdsParser parser, SNIHandle physicalConnection, bo _lastSuccessfulIOTimer = parser._physicalStateObj._lastSuccessfulIOTimer; } - internal SSPIContextProvider CreateSSPIContextProvider() => new NativeSSPIContextProvider(); - //////////////// // Properties // //////////////// @@ -114,127 +86,10 @@ internal SNIHandle Handle } } - internal uint Status - { - get - { - if (_sessionHandle != null) - { - return _sessionHandle.Status; - } - else - { // SQL BU DT 395431. - return TdsEnums.SNI_UNINITIALIZED; - } - } - } - - internal SessionHandle SessionHandle => new SessionHandle(Handle); - ///////////////////// // General methods // ///////////////////// - // This method is only called by the command or datareader as a result of a user initiated - // cancel request. - internal void Cancel(int objectID) - { - bool hasLock = false; - try - { - // Keep looping until we either grabbed the lock (and therefore sent attention) or the connection closes\breaks - while ((!hasLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) - { - Monitor.TryEnter(this, WaitForCancellationLockPollTimeout, ref hasLock); - if (hasLock) - { // Lock for the time being - since we need to synchronize the attention send. - // This lock is also protecting against concurrent close and async continuations - - // don't allow objectID -1 since it is reserved for 'not associated with a command' - // yes, the 2^32-1 comand won't cancel - but it also won't cancel when we don't want it - if ((!_cancelled) && (objectID == _allowObjectID) && (objectID != -1)) - { - _cancelled = true; - - if (HasPendingData && !_attentionSent) - { - bool hasParserLock = false; - // Keep looping until we have the parser lock (and so are allowed to write), or the connection closes\breaks - while ((!hasParserLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) - { - try - { - _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false, timeout: WaitForCancellationLockPollTimeout, lockTaken: ref hasParserLock); - if (hasParserLock) - { - _parser.Connection.ThreadHasParserLockForClose = true; - SendAttention(); - } - } - finally - { - if (hasParserLock) - { - if (_parser.Connection.ThreadHasParserLockForClose) - { - _parser.Connection.ThreadHasParserLockForClose = false; - } - _parser.Connection._parserLock.Release(); - } - } - } - } - } - } - } - } - finally - { - if (hasLock) - { - Monitor.Exit(this); - } - } - } - - private void ResetCancelAndProcessAttention() - { - // This method is shared by CloseSession initiated by DataReader.Close or completed - // command execution, as well as the session reclamation code for cases where the - // DataReader is opened and then GC'ed. - lock (this) - { - // Reset cancel state. - _cancelled = false; - _allowObjectID = -1; - - if (_attentionSent) - { - // Make sure we're cleaning up the AttentionAck if Cancel happened before taking the lock. - // We serialize Cancel/CloseSession to prevent a race condition between these two states. - // The problem is that both sending and receiving attentions are time taking - // operations. -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#endif //DEBUG - Parser.ProcessPendingAck(this); -#if DEBUG - } - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG - } - SetTimeoutStateStopped(); - } - } - private ConsumerInfo CreateConsumerInfo(bool async) { ConsumerInfo myInfo = new ConsumerInfo(); @@ -257,7 +112,7 @@ internal void CreatePhysicalSNIHandle( string serverName, TimeoutTimer timeout, out byte[] instanceName, - byte[] spnBuffer, + ref string spn, bool flushCache, bool async, bool fParallel, @@ -274,32 +129,13 @@ internal void CreatePhysicalSNIHandle( _ = SQLFallbackDNSCache.Instance.GetDNSInfo(cachedFQDN, out SQLDNSInfo cachedDNSInfo); - _sessionHandle = new SNIHandle(myInfo, serverName, spnBuffer, timeout.MillisecondsRemainingInt, + _sessionHandle = new SNIHandle(myInfo, serverName, ref spn, timeout.MillisecondsRemainingInt, out instanceName, flushCache, !async, fParallel, transparentNetworkResolutionState, totalTimeout, ipPreference, cachedDNSInfo, hostNameInCertificate); } - internal bool IsPacketEmpty(PacketHandle readPacket) => readPacket == default; - - internal PacketHandle ReadSyncOverAsync(int timeoutRemaining, out uint error) - { - SNIHandle handle = Handle ?? throw ADP.ClosedConnectionError(); - PacketHandle readPacket = default; - error = SNINativeMethodWrapper.SNIReadSyncOverAsync(handle, ref readPacket, timeoutRemaining); - return readPacket; - } - - internal PacketHandle ReadAsync(SessionHandle handle, out uint error) - { - PacketHandle readPacket = default; - error = SNINativeMethodWrapper.SNIReadAsync(handle.NativeHandle, ref readPacket); - return readPacket; - } - - internal uint CheckConnection() => SNINativeMethodWrapper.SNICheckConnection(Handle); + internal uint CheckConnection() => SniNativeWrapper.SniCheckConnection(Handle); - internal void ReleasePacket(PacketHandle syncReadPacket) => SNINativeMethodWrapper.SNIPacketRelease(syncReadPacket); - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] internal int DecrementPendingCallbacks(bool release) { @@ -359,35 +195,7 @@ internal void Dispose() } } - if (_writePacketCache != null) - { - lock (_writePacketLockObject) - { - RuntimeHelpers.PrepareConstrainedRegions(); - try - { } - finally - { - _writePacketCache.Dispose(); - // Do not set _writePacketCache to null, just in case a WriteAsyncCallback completes after this point - } - } - } - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - internal int IncrementPendingCallbacks() - { - int remaining = Interlocked.Increment(ref _pendingCallbacks); - - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, after incrementing _pendingCallbacks: {1}", ObjectID, _pendingCallbacks); - Debug.Assert(0 < remaining && remaining <= 3, $"_pendingCallbacks values is invalid after incrementing: {remaining}"); - return remaining; - } - - internal void StartSession(int objectID) - { - _allowObjectID = objectID; + DisposePacketCache(); } /// @@ -416,7 +224,7 @@ internal bool ValidateSNIConnection() SNIHandle handle = Handle; if (handle != null) { - error = SNINativeMethodWrapper.SNICheckConnection(handle); + error = SniNativeWrapper.SniCheckConnection(handle); } } finally @@ -429,8 +237,6 @@ internal bool ValidateSNIConnection() // This method should only be called by ReadSni! If not - it may have problems with timeouts! private void ReadSniError(TdsParserStateObject stateObj, uint error) { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadSniSyncError"); // you need to setup for a thread abort somewhere before you call this method - if (TdsEnums.SNI_WAIT_TIMEOUT == error) { Debug.Assert(_syncOverAsync, "Should never reach here with async on!"); @@ -543,7 +349,7 @@ public void ProcessSniPacket(PacketHandle packet, uint error) { uint dataSize = 0; - uint getDataError = SNINativeMethodWrapper.SNIPacketGetData(packet, _inBuff, ref dataSize); + uint getDataError = SniPacketGetData(packet, _inBuff, ref dataSize); if (getDataError == TdsEnums.SNI_SUCCESS) { @@ -581,23 +387,6 @@ public void ProcessSniPacket(PacketHandle packet, uint error) } } - private void ChangeNetworkPacketTimeout(int dueTime, int period) - { - Timer networkPacketTimeout = _networkPacketTimeout; - if (networkPacketTimeout != null) - { - try - { - networkPacketTimeout.Change(dueTime, period); - } - catch (ObjectDisposedException) - { - // _networkPacketTimeout is set to null before Disposing, but there is still a slight chance - // that object was disposed after we took a copy - } - } - } - public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) { // Key never used. @@ -629,7 +418,7 @@ public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) bool processFinallyBlock = true; try { - Debug.Assert(IntPtr.Zero == packet || IntPtr.Zero != packet && source != null, "AsyncResult null on callback"); + Debug.Assert(CheckPacket(packet, source), "AsyncResult null on callback"); if (_parser.MARSOn) { @@ -641,7 +430,7 @@ public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) // The timer thread may be unreliable under high contention scenarios. It cannot be // assumed that the timeout has happened on the timer thread callback. Check the timeout - // synchrnously and then call OnTimeoutSync to force an atomic change of state. + // synchronously and then call OnTimeoutSync to force an atomic change of state. if (TimeoutHasExpired) { OnTimeoutSync(asyncClose: true); @@ -675,7 +464,7 @@ public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) { if (_executionContext != null) { - ExecutionContext.Run(_executionContext, (state) => source.TrySetResult(null), null); + ExecutionContext.Run(_executionContext, s_readAsyncCallbackComplete, source); } else { @@ -686,7 +475,7 @@ public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) { if (_executionContext != null) { - ExecutionContext.Run(_executionContext, (state) => ReadAsyncCallbackCaptureException(source), null); + ExecutionContext.Run(_executionContext, state => ReadAsyncCallbackCaptureException((TaskCompletionSource)state), source); } else { @@ -699,48 +488,6 @@ public void ReadAsyncCallback(IntPtr key, PacketHandle packet, uint error) } } - private void ReadAsyncCallbackCaptureException(TaskCompletionSource source) - { - bool captureSuccess = false; - try - { - if (_hasErrorOrWarning) - { - // Do the close on another thread, since we don't want to block the callback thread - ThrowExceptionAndWarning(asyncClose: true); - } - else if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken)) - { - // Connection was closed by another thread before we parsed the packet, so no error was added to the collection - throw ADP.ClosedConnectionError(); - } - } - catch (Exception ex) - { - if (source.TrySetException(ex)) - { - // There was an exception, and it was successfully stored in the task - captureSuccess = true; - } - } - - if (!captureSuccess) - { - // Either there was no exception, or the task was already completed - // This is unusual, but possible if a fatal timeout occurred on another thread (which should mean that the connection is now broken) - Debug.Assert(_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed || _parser.Connection.IsConnectionDoomed, "Failed to capture exception while the connection was still healthy"); - - // The safest thing to do is to ensure that the connection is broken and attempt to cancel the task - // This must be done from another thread to not block the callback thread - Task.Factory.StartNew(() => - { - _parser.State = TdsParserState.Broken; - _parser.Connection.BreakConnection(); - source.TrySetCanceled(); - }); - } - } - #pragma warning disable 420 // a reference to a volatile field will not be treated as volatile public void WriteAsyncCallback(IntPtr key, PacketHandle packet, uint sniError) @@ -832,204 +579,6 @@ public void WriteAsyncCallback(IntPtr key, PacketHandle packet, uint sniError) // Network/Packet Writing & Processing // ///////////////////////////////////////// - // - // Takes a secure string and offsets and saves them for a write latter when the information is written out to SNI Packet - // This method is provided to better handle the life cycle of the clear text of the secure string - // This method also ensures that the clear text is not held in the unpined managed buffer so that it avoids getting moved around by CLR garbage collector - // TdsParserStaticMethods.EncryptPassword operation is also done in the unmanaged buffer for the clear text later - // - internal void WriteSecureString(SecureString secureString) - { - TdsParser.ReliabilitySection.Assert("unreliable call to WriteSecureString"); // you need to setup for a thread abort somewhere before you call this method - - Debug.Assert(_securePasswords[0] == null || _securePasswords[1] == null, "There are more than two secure passwords"); - - int index = _securePasswords[0] != null ? 1 : 0; - - _securePasswords[index] = secureString; - _securePasswordOffsetsInBuffer[index] = _outBytesUsed; - - // loop through and write the entire array - int lengthInBytes = secureString.Length * 2; - - // It is guaranteed both secure password and secure change password should fit into the first packet - // Given current TDS format and implementation it is not possible that one of secure string is the last item and exactly fill up the output buffer - // if this ever happens and it is correct situation, the packet needs to be written out after _outBytesUsed is update - Debug.Assert((_outBytesUsed + lengthInBytes) < _outBuff.Length, "Passwords cannot be split into two different packet or the last item which fully fill up _outBuff!!!"); - - _outBytesUsed += lengthInBytes; - } - - internal void ResetSecurePasswordsInformation() - { - for (int i = 0; i < _securePasswords.Length; ++i) - { - _securePasswords[i] = null; - _securePasswordOffsetsInBuffer[i] = 0; - } - } - - internal Task WaitForAccumulatedWrites() - { - // Checked for stored exceptions -#pragma warning disable 420 // A reference to a volatile field will not be treated as volatile - Disabling since the Interlocked APIs are volatile aware - Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); - if (delayedException != null) - { - throw delayedException; - } -#pragma warning restore 420 - - if (_asyncWriteCount == 0) - { - return null; - } - - _writeCompletionSource = new TaskCompletionSource(); - Task task = _writeCompletionSource.Task; - - // Ensure that _writeCompletionSource is set before checking state - Interlocked.MemoryBarrier(); - - // Now that we have set _writeCompletionSource, check if parser is closed or broken - if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken)) - { - throw ADP.ClosedConnectionError(); - } - - // Check for stored exceptions -#pragma warning disable 420 // A reference to a volatile field will not be treated as volatile - Disabling since the Interlocked APIs are volatile aware - delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); - if (delayedException != null) - { - throw delayedException; - } -#pragma warning restore 420 - - // If there are no outstanding writes, see if we can shortcut and return null - if ((_asyncWriteCount == 0) && ((!task.IsCompleted) || (task.Exception == null))) - { - task = null; - } - - return task; - } - - // Takes in a single byte and writes it to the buffer. If the buffer is full, it is flushed - // and then the buffer is re-initialized in flush() and then the byte is put in the buffer. - internal void WriteByte(byte b) - { - TdsParser.ReliabilitySection.Assert("unreliable call to WriteByte"); // you need to setup for a thread abort somewhere before you call this method - - Debug.Assert(_outBytesUsed <= _outBuff.Length, "ERROR - TDSParser: _outBytesUsed > _outBuff.Length"); - - // check to make sure we haven't used the full amount of space available in the buffer, if so, flush it - if (_outBytesUsed == _outBuff.Length) - { - WritePacket(TdsEnums.SOFTFLUSH, canAccumulate: true); - } - // set byte in buffer and increment the counter for number of bytes used in the out buffer - _outBuff[_outBytesUsed++] = b; - } - - internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null) - { - try - { - TdsParser.ReliabilitySection.Assert("unreliable call to WriteByteArray"); // you need to setup for a thread abort somewhere before you call this method - - bool async = _parser._asyncWrite; // NOTE: We are capturing this now for the assert after the Task is returned, since WritePacket will turn off async if there is an exception - Debug.Assert(async || _asyncWriteCount == 0); - // Do we have to send out in packet size chunks, or can we rely on netlib layer to break it up? - // would prefer to do something like: - // - // if (len > what we have room for || len > out buf) - // flush buffer - // UnsafeNativeMethods.Write(b) - // - - int offset = offsetBuffer; - - Debug.Assert(b.Length >= len, "Invalid length sent to WriteByteArray()!"); - - // loop through and write the entire array - do - { - if ((_outBytesUsed + len) > _outBuff.Length) - { - // If the remainder of the data won't fit into the buffer, then we have to put - // whatever we can into the buffer, and flush that so we can then put more into - // the buffer on the next loop of the while. - - int remainder = _outBuff.Length - _outBytesUsed; - - // write the remainder - Buffer.BlockCopy(b, offset, _outBuff, _outBytesUsed, remainder); - - // handle counters - offset += remainder; - _outBytesUsed += remainder; - len -= remainder; - - Task packetTask = WritePacket(TdsEnums.SOFTFLUSH, canAccumulate); - - if (packetTask != null) - { - Task task = null; - Debug.Assert(async, "Returned task in sync mode"); - if (completion == null) - { - completion = new TaskCompletionSource(); - task = completion.Task; // we only care about return from topmost call, so do not access Task property in other cases - } - WriteByteArraySetupContinuation(b, len, completion, offset, packetTask); - return task; - } - - } - else - { - //((stateObj._outBytesUsed + len) <= stateObj._outBuff.Length ) - // Else the remainder of the string will fit into the buffer, so copy it into the - // buffer and then break out of the loop. - - Buffer.BlockCopy(b, offset, _outBuff, _outBytesUsed, len); - - // handle out buffer bytes used counter - _outBytesUsed += len; - break; - } - } while (len > 0); - - if (completion != null) - { - completion.SetResult(null); - } - return null; - } - catch (Exception e) - { - if (completion != null) - { - completion.SetException(e); - return null; - } - else - { - throw; - } - } - } - - // This is in its own method to avoid always allocating the lambda in WriteByteArray - private void WriteByteArraySetupContinuation(byte[] b, int len, TaskCompletionSource completion, int offset, Task packetTask) - { - AsyncHelper.ContinueTask(packetTask, completion, - () => WriteByteArray(b, len: len, offsetBuffer: offset, canAccumulate: false, completion: completion), - connectionToDoom: _parser.Connection - ); - } - // Dumps contents of buffer to SNI for network write. internal Task WritePacket(byte flushMode, bool canAccumulate = false) { @@ -1046,7 +595,8 @@ internal Task WritePacket(byte flushMode, bool canAccumulate = false) // So we need to avoid this check prior to login completing state == TdsParserState.OpenLoggedIn && !_bulkCopyOpperationInProgress // ignore the condition checking for bulk copy - && _outBytesUsed == (_outputHeaderLen + BitConverter.ToInt32(_outBuff, _outputHeaderLen)) + && _outBytesUsed == (_outputHeaderLen + + BinaryPrimitives.ReadInt32LittleEndian(_outBuff.AsSpan(_outputHeaderLen))) && _outputPacketCount == 0 || _outBytesUsed == _outputHeaderLen && _outputPacketCount == 0) @@ -1099,35 +649,19 @@ internal Task WritePacket(byte flushMode, bool canAccumulate = false) if (willCancel) { // If we have been canceled, then ensure that we write the ATTN packet as well +#if NET + task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket); +#else task = AsyncHelper.CreateContinuationTask(task, CancelWritePacket, _parser.Connection); +#endif } return task; } - private void CancelWritePacket() - { - Debug.Assert(_cancelled, "Should not call CancelWritePacket if _cancelled is not set"); - - _parser.Connection.ThreadHasParserLockForClose = true; // In case of error, let the connection know that we are holding the lock - try - { - // Send the attention and wait for the ATTN_ACK - SendAttention(); - ResetCancelAndProcessAttention(); - - // Let the caller know that we've given up - throw SQL.OperationCancelled(); - } - finally - { - _parser.Connection.ThreadHasParserLockForClose = false; - } - } - #pragma warning disable 420 // a reference to a volatile field will not be treated as volatile - private Task SNIWritePacket(SNIHandle handle, SNIPacket packet, out uint sniError, bool canAccumulate, bool callerHasConnectionLock, bool asyncClose = false) + private Task SNIWritePacket(PacketHandle packet, out uint sniError, bool canAccumulate, bool callerHasConnectionLock, bool asyncClose = false) { // Check for a stored exception Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); @@ -1169,7 +703,7 @@ private Task SNIWritePacket(SNIHandle handle, SNIPacket packet, out uint sniErro } finally { - sniError = SNINativeMethodWrapper.SNIWritePacket(handle, packet, sync); + sniError = WritePacket(packet, sync); } if (sniError == TdsEnums.SNI_SUCCESS_IO_PENDING) @@ -1263,8 +797,6 @@ private Task SNIWritePacket(SNIHandle handle, SNIPacket packet, out uint sniErro #pragma warning restore 420 - internal bool IsValidPacket(PacketHandle packetPointer) => packetPointer != default; - // Sends an attention signal - executing thread will consume attn. internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = false) { @@ -1278,10 +810,7 @@ internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = fa return; } - SNIPacket attnPacket = new SNIPacket(Handle); - _sniAsyncAttnPacket = attnPacket; - - SNINativeMethodWrapper.SNIPacketSetData(attnPacket, SQL.AttentionHeader, TdsEnums.HEADER_LEN, null, null); + PacketHandle attnPacket = CreateAndSetAttentionPacket(); RuntimeHelpers.PrepareConstrainedRegions(); try @@ -1311,7 +840,7 @@ internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = fa } _parser._asyncWrite = false; // stop async write - SNIWritePacket(Handle, attnPacket, out _, canAccumulate: false, callerHasConnectionLock: false, asyncClose); + SNIWritePacket(attnPacket, out _, canAccumulate: false, callerHasConnectionLock: false, asyncClose); SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObject.SendAttention | Info | State Object Id {0}, Sent Attention.", _objectID); } finally @@ -1341,14 +870,23 @@ internal void SendAttention(bool mustTakeWriteLock = false, bool asyncClose = fa } } + internal PacketHandle CreateAndSetAttentionPacket() + { + SNIPacket attnPacket = new SNIPacket(Handle); + _sniAsyncAttnPacket = attnPacket; + SniNativeWrapper.SniPacketSetData(attnPacket, SQL.AttentionHeader, TdsEnums.HEADER_LEN, null, null); + return PacketHandle.FromNativePacket(attnPacket); + } + private Task WriteSni(bool canAccumulate) { // Prepare packet, and write to packet. - SNIPacket packet = GetResetWritePacket(); - SNINativeMethodWrapper.SNIPacketSetData(packet, _outBuff, _outBytesUsed, _securePasswords, _securePasswordOffsetsInBuffer); + PacketHandle packet = GetResetWritePacket(_outBytesUsed); + SNIPacket nativePacket = packet.NativePacket; + SniNativeWrapper.SniPacketSetData(nativePacket, _outBuff, _outBytesUsed, _securePasswords, _securePasswordOffsetsInBuffer); Debug.Assert(Parser.Connection._parserLock.ThreadMayHaveLock(), "Thread is writing without taking the connection lock"); - Task task = SNIWritePacket(Handle, packet, out _, canAccumulate, callerHasConnectionLock: true); + Task task = SNIWritePacket(packet, out _, canAccumulate, callerHasConnectionLock: true); // Check to see if the timeout has occurred. This time out code is special case code to allow BCP writes to timeout. Eventually we should make all writes timeout. if (_bulkCopyOpperationInProgress && 0 == GetTimeoutRemaining()) @@ -1396,90 +934,10 @@ private Task WriteSni(bool canAccumulate) return task; } - internal SNIPacket GetResetWritePacket() - { - if (_sniPacket != null) - { - SNINativeMethodWrapper.SNIPacketReset(Handle, IoType.WRITE, _sniPacket, ConsumerNumber.SNI_Consumer_SNI); - } - else - { - lock (_writePacketLockObject) - { - _sniPacket = _writePacketCache.Take(Handle); - } - } - return _sniPacket; - } - - internal void ClearAllWritePackets() - { - if (_sniPacket != null) - { - _sniPacket.Dispose(); - _sniPacket = null; - } - lock (_writePacketLockObject) - { - Debug.Assert(_pendingWritePackets.Count == 0 && _asyncWriteCount == 0, "Should not clear all write packets if there are packets pending"); - _writePacketCache.Clear(); - } - } - - private IntPtr AddPacketToPendingList(SNIPacket packet) - { - Debug.Assert(packet == _sniPacket, "Adding a packet other than the current packet to the pending list"); - _sniPacket = null; - IntPtr pointer = packet.DangerousGetHandle(); - - lock (_writePacketLockObject) - { - _pendingWritePackets.Add(pointer, packet); - } - - return pointer; - } - - private void RemovePacketFromPendingList(IntPtr pointer) - { - SNIPacket recoveredPacket; - - lock (_writePacketLockObject) - { - if (_pendingWritePackets.TryGetValue(pointer, out recoveredPacket)) - { - _pendingWritePackets.Remove(pointer); - _writePacketCache.Add(recoveredPacket); - } -#if DEBUG - else - { - Debug.Assert(false, "Removing a packet from the pending list that was never added to it"); - } -#endif - } - } - ////////////////////////////////////////////// // Statistics, Tracing, and related methods // ////////////////////////////////////////////// - private void SniReadStatisticsAndTracing() - { - SqlStatistics statistics = Parser.Statistics; - if (statistics != null) - { - if (statistics.WaitForReply) - { - statistics.SafeIncrement(ref statistics._serverRoundtrips); - statistics.ReleaseAndUpdateNetworkServerTimer(); - } - - statistics.SafeAdd(ref statistics._bytesReceived, _inBytesRead); - statistics.SafeIncrement(ref statistics._buffersReceived); - } - } - private void SniWriteStatisticsAndTracing() { SqlStatistics statistics = _parser.Statistics; @@ -1521,356 +979,5 @@ private void SniWriteStatisticsAndTracing() } SqlClientEventSource.Log.TryAdvancedTraceBinEvent("TdsParser.WritePacket | INFO | ADV | State Object Id {0}, Packet sent. Out buffer: {1}, Out Bytes Used: {2}", ObjectID, _outBuff, _outBytesUsed); } - - [Conditional("DEBUG")] - private void AssertValidState() - { - if (_inBytesUsed < 0 || _inBytesRead < 0) - { - Debug.Fail($"Invalid TDS Parser State: either _inBytesUsed or _inBytesRead is negative: {_inBytesUsed}, {_inBytesRead}"); - } - else if (_inBytesUsed > _inBytesRead) - { - Debug.Fail($"Invalid TDS Parser State: _inBytesUsed > _inBytesRead: {_inBytesUsed} > {_inBytesRead}"); - } - - Debug.Assert(_inBytesPacket >= 0, "Packet must not be negative"); - } - - - ////////////////////////////////////////////// - // Errors and Warnings // - ////////////////////////////////////////////// - - /// - /// True if there is at least one error or warning (not counting the pre-attention errors\warnings) - /// - internal bool HasErrorOrWarning - { - get - { - return _hasErrorOrWarning; - } - } - - /// - /// Adds an error to the error collection - /// - /// - internal void AddError(SqlError error) - { - Debug.Assert(error != null, "Trying to add a null error"); - - // Switch to sync once we see an error - _syncOverAsync = true; - - lock (_errorAndWarningsLock) - { - _hasErrorOrWarning = true; - if (_errors == null) - { - _errors = new SqlErrorCollection(); - } - _errors.Add(error); - } - } - - /// - /// Gets the number of errors currently in the error collection - /// - internal int ErrorCount - { - get - { - int count = 0; - lock (_errorAndWarningsLock) - { - if (_errors != null) - { - count = _errors.Count; - } - } - return count; - } - } - - /// - /// Adds an warning to the warning collection - /// - /// - internal void AddWarning(SqlError error) - { - Debug.Assert(error != null, "Trying to add a null error"); - - // Switch to sync once we see a warning - _syncOverAsync = true; - - lock (_errorAndWarningsLock) - { - _hasErrorOrWarning = true; - if (_warnings == null) - { - _warnings = new SqlErrorCollection(); - } - _warnings.Add(error); - } - } - - /// - /// Gets the number of warnings currently in the warning collection - /// - internal int WarningCount - { - get - { - int count = 0; - lock (_errorAndWarningsLock) - { - if (_warnings != null) - { - count = _warnings.Count; - } - } - return count; - } - } - - protected PacketHandle EmptyReadPacket => default; - - internal int PreAttentionErrorCount - { - get - { - int count = 0; - lock (_errorAndWarningsLock) - { - if (_preAttentionErrors != null) - { - count = _preAttentionErrors.Count; - } - } - return count; - } - } - - /// - /// Gets the number of errors currently in the pre-attention warning collection - /// - internal int PreAttentionWarningCount - { - get - { - int count = 0; - lock (_errorAndWarningsLock) - { - if (_preAttentionWarnings != null) - { - count = _preAttentionWarnings.Count; - } - } - return count; - } - } - - /// - /// Gets the full list of errors and warnings (including the pre-attention ones), then wipes all error and warning lists - /// - /// If true, the connection should be broken - /// An array containing all of the errors and warnings - internal SqlErrorCollection GetFullErrorAndWarningCollection(out bool broken) - { - SqlErrorCollection allErrors = new SqlErrorCollection(); - broken = false; - - lock (_errorAndWarningsLock) - { - _hasErrorOrWarning = false; - - // Merge all error lists, then reset them - AddErrorsToCollection(_errors, ref allErrors, ref broken); - AddErrorsToCollection(_warnings, ref allErrors, ref broken); - _errors = null; - _warnings = null; - - // We also process the pre-attention error lists here since, if we are here and they are populated, then an error occurred while sending attention so we should show the errors now (otherwise they'd be lost) - AddErrorsToCollection(_preAttentionErrors, ref allErrors, ref broken); - AddErrorsToCollection(_preAttentionWarnings, ref allErrors, ref broken); - _preAttentionErrors = null; - _preAttentionWarnings = null; - } - - return allErrors; - } - - private void AddErrorsToCollection(SqlErrorCollection inCollection, ref SqlErrorCollection collectionToAddTo, ref bool broken) - { - if (inCollection != null) - { - foreach (SqlError error in inCollection) - { - collectionToAddTo.Add(error); - broken |= (error.Class >= TdsEnums.FATAL_ERROR_CLASS); - } - } - } - - /// - /// Stores away current errors and warnings so that an attention can be processed - /// - internal void StoreErrorAndWarningForAttention() - { - lock (_errorAndWarningsLock) - { - Debug.Assert(_preAttentionErrors == null && _preAttentionWarnings == null, "Can't store errors for attention because there are already errors stored"); - - _hasErrorOrWarning = false; - - _preAttentionErrors = _errors; - _preAttentionWarnings = _warnings; - - _errors = null; - _warnings = null; - } - } - - /// - /// Restores errors and warnings that were stored in order to process an attention - /// - internal void RestoreErrorAndWarningAfterAttention() - { - lock (_errorAndWarningsLock) - { - Debug.Assert(_errors == null && _warnings == null, "Can't restore errors after attention because there are already other errors"); - - _hasErrorOrWarning = (((_preAttentionErrors != null) && (_preAttentionErrors.Count > 0)) || ((_preAttentionWarnings != null) && (_preAttentionWarnings.Count > 0))); - - _errors = _preAttentionErrors; - _warnings = _preAttentionWarnings; - - _preAttentionErrors = null; - _preAttentionWarnings = null; - } - } - - /// - /// Checks if an error is stored in _error and, if so, throws an error - /// - internal void CheckThrowSNIException() - { - if (HasErrorOrWarning) - { - ThrowExceptionAndWarning(); - } - } - - /// - /// Debug Only: Ensures that the TdsParserStateObject has no lingering state and can safely be re-used - /// - [Conditional("DEBUG")] - internal void AssertStateIsClean() - { - // If our TdsParser is closed or broken, then we don't really care about our state - TdsParser parser = _parser; - if ((parser != null) && (parser.State != TdsParserState.Closed) && (parser.State != TdsParserState.Broken)) - { - // Async reads - Debug.Assert(_snapshot == null && !_snapshotReplay, "StateObj has leftover snapshot state"); - Debug.Assert(!_asyncReadWithoutSnapshot, "StateObj has AsyncReadWithoutSnapshot still enabled"); - Debug.Assert(_executionContext == null, "StateObj has a stored execution context from an async read"); - // Async writes - Debug.Assert(_asyncWriteCount == 0, "StateObj still has outstanding async writes"); - Debug.Assert(_delayedWriteAsyncCallbackException == null, "StateObj has an unobserved exceptions from an async write"); - // Attention\Cancellation\Timeouts - Debug.Assert(!HasReceivedAttention && !_attentionSent && !_attentionSending, $"StateObj is still dealing with attention: Sent: {_attentionSent}, Received: {HasReceivedAttention}, Sending: {_attentionSending}"); - Debug.Assert(!_cancelled, "StateObj still has cancellation set"); - Debug.Assert(_timeoutState == TimeoutState.Stopped, "StateObj still has internal timeout set"); - // Errors and Warnings - Debug.Assert(!_hasErrorOrWarning, "StateObj still has stored errors or warnings"); - } - } - -#if DEBUG - internal void CompletePendingReadWithSuccess(bool resetForcePendingReadsToWait) - { - TaskCompletionSource realNetworkPacketTaskSource = _realNetworkPacketTaskSource; - TaskCompletionSource networkPacketTaskSource = _networkPacketTaskSource; - - Debug.Assert(s_forcePendingReadsToWaitForUser, "Not forcing pends to wait for user - can't force complete"); - Debug.Assert(networkPacketTaskSource != null, "No pending read to complete"); - - try - { - if (realNetworkPacketTaskSource != null) - { - // Wait for the real read to complete - realNetworkPacketTaskSource.Task.Wait(); - } - } - finally - { - if (networkPacketTaskSource != null) - { - if (resetForcePendingReadsToWait) - { - s_forcePendingReadsToWaitForUser = false; - } - - networkPacketTaskSource.TrySetResult(null); - } - } - } - - internal void CompletePendingReadWithFailure(int errorCode, bool resetForcePendingReadsToWait) - { - TaskCompletionSource realNetworkPacketTaskSource = _realNetworkPacketTaskSource; - TaskCompletionSource networkPacketTaskSource = _networkPacketTaskSource; - - Debug.Assert(s_forcePendingReadsToWaitForUser, "Not forcing pends to wait for user - can't force complete"); - Debug.Assert(networkPacketTaskSource != null, "No pending read to complete"); - - try - { - if (realNetworkPacketTaskSource != null) - { - // Wait for the real read to complete - realNetworkPacketTaskSource.Task.Wait(); - } - } - finally - { - if (networkPacketTaskSource != null) - { - if (resetForcePendingReadsToWait) - { - s_forcePendingReadsToWaitForUser = false; - } - - AddError(new SqlError(errorCode, 0x00, TdsEnums.FATAL_ERROR_CLASS, _parser.Server, string.Empty, string.Empty, 0)); - try - { - ThrowExceptionAndWarning(); - } - catch (Exception ex) - { - networkPacketTaskSource.TrySetException(ex); - } - } - } - } -#endif - - internal void CloneCleanupAltMetaDataSetArray() - { - if (_snapshot != null) - { - _snapshot.CloneCleanupAltMetaDataSetArray(); - } - } - - - sealed partial class StateSnapshot - { - - } } } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs new file mode 100644 index 0000000000..2ad67cf2f4 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/TdsParserStateObjectNative.cs @@ -0,0 +1,197 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Interop.Windows.Sni; +using Microsoft.Data.Common; + +namespace Microsoft.Data.SqlClient +{ + internal class TdsParserStateObjectNative : TdsParserStateObject + { + private readonly WritePacketCache _writePacketCache = new WritePacketCache(); // Store write packets that are ready to be re-used + + private readonly Dictionary _pendingWritePackets = new Dictionary(); // Stores write packets that have been sent to SNI, but have not yet finished writing (i.e. we are waiting for SNI's callback) + + internal TdsParserStateObjectNative(TdsParser parser, TdsParserStateObject physicalConnection, bool async) + : base(parser, physicalConnection.Handle, async) + { + } + + public TdsParserStateObjectNative(TdsParser parser) + : base(parser) + { + } + + //////////////// + // Properties // + //////////////// + + internal override uint Status => _sessionHandle != null ? _sessionHandle.Status : TdsEnums.SNI_UNINITIALIZED; + + internal override SessionHandle SessionHandle => SessionHandle.FromNativeHandle(_sessionHandle); + + protected override PacketHandle EmptyReadPacket => PacketHandle.FromNativePointer(default); + + internal override Guid? SessionId => default; + + protected override uint SniPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize) + { + Debug.Assert(packet.Type == PacketHandle.NativePointerType, "unexpected packet type when requiring NativePointer"); + return SniNativeWrapper.SniPacketGetData(packet.NativePointer, _inBuff, ref dataSize); + } + + protected override bool CheckPacket(PacketHandle packet, TaskCompletionSource source) + { + Debug.Assert(packet.Type == PacketHandle.NativePointerType, "unexpected packet type when requiring NativePointer"); + IntPtr ptr = packet.NativePointer; + return IntPtr.Zero == ptr || IntPtr.Zero != ptr && source != null; + } + + protected override void RemovePacketFromPendingList(PacketHandle ptr) + { + Debug.Assert(ptr.Type == PacketHandle.NativePointerType, "unexpected packet type when requiring NativePointer"); + IntPtr pointer = ptr.NativePointer; + + lock (_writePacketLockObject) + { + if (_pendingWritePackets.TryGetValue(pointer, out SNIPacket recoveredPacket)) + { + _pendingWritePackets.Remove(pointer); + _writePacketCache.Add(recoveredPacket); + } +#if DEBUG + else + { + Debug.Fail("Removing a packet from the pending list that was never added to it"); + } +#endif + } + } + + internal override bool IsFailedHandle() => _sessionHandle.Status != TdsEnums.SNI_SUCCESS; + + internal override bool IsPacketEmpty(PacketHandle readPacket) + { + Debug.Assert(readPacket.Type == PacketHandle.NativePointerType || readPacket.Type == 0, "unexpected packet type when requiring NativePointer"); + return IntPtr.Zero == readPacket.NativePointer; + } + + internal override void ReleasePacket(PacketHandle syncReadPacket) + { + Debug.Assert(syncReadPacket.Type == PacketHandle.NativePointerType, "unexpected packet type when requiring NativePointer"); + SniNativeWrapper.SniPacketRelease(syncReadPacket.NativePointer); + } + + internal override PacketHandle ReadAsync(SessionHandle handle, out uint error) + { + IntPtr readPacketPtr = IntPtr.Zero; + error = SniNativeWrapper.SniReadAsync(handle.NativeHandle, ref readPacketPtr); + return PacketHandle.FromNativePointer(readPacketPtr); + } + + internal override PacketHandle ReadSyncOverAsync(int timeoutRemaining, out uint error) + { + SNIHandle handle = Handle ?? throw ADP.ClosedConnectionError(); + IntPtr readPacketPtr = IntPtr.Zero; + error = SniNativeWrapper.SniReadSyncOverAsync(handle, ref readPacketPtr, timeoutRemaining); + return PacketHandle.FromNativePointer(readPacketPtr); + } + + internal override uint WritePacket(PacketHandle packet, bool sync) + { + Debug.Assert(packet.Type == PacketHandle.NativePacketType, "unexpected packet type when requiring NativePacket"); + return SniNativeWrapper.SniWritePacket(Handle, packet.NativePacket, sync); + } + + internal override PacketHandle AddPacketToPendingList(PacketHandle packetToAdd) + { + Debug.Assert(packetToAdd.Type == PacketHandle.NativePacketType, "unexpected packet type when requiring NativePacket"); + SNIPacket packet = packetToAdd.NativePacket; + Debug.Assert(packet == _sniPacket, "Adding a packet other than the current packet to the pending list"); + _sniPacket = null; + IntPtr pointer = packet.DangerousGetHandle(); + + lock (_writePacketLockObject) + { + _pendingWritePackets.Add(pointer, packet); + } + + return PacketHandle.FromNativePointer(pointer); + } + + internal override bool IsValidPacket(PacketHandle packetPointer) + { + Debug.Assert(packetPointer.Type == PacketHandle.NativePointerType || packetPointer.Type == PacketHandle.NativePacketType, "unexpected packet type when requiring NativePointer"); + + return (packetPointer.Type == PacketHandle.NativePointerType && packetPointer.NativePointer != IntPtr.Zero) + || (packetPointer.Type == PacketHandle.NativePacketType && packetPointer.NativePacket != null); + } + + internal override PacketHandle GetResetWritePacket(int dataSize) + { + if (_sniPacket != null) + { + SniNativeWrapper.SniPacketReset(Handle, IoType.WRITE, _sniPacket, ConsumerNumber.SNI_Consumer_SNI); + } + else + { + lock (_writePacketLockObject) + { + _sniPacket = _writePacketCache.Take(Handle); + } + } + return PacketHandle.FromNativePacket(_sniPacket); + } + + internal override void ClearAllWritePackets() + { + if (_sniPacket != null) + { + _sniPacket.Dispose(); + _sniPacket = null; + } + lock (_writePacketLockObject) + { + Debug.Assert(_pendingWritePackets.Count == 0 && _asyncWriteCount == 0, "Should not clear all write packets if there are packets pending"); + _writePacketCache.Clear(); + } + } + + internal override uint SniGetConnectionId(ref Guid clientConnectionId) + => SniNativeWrapper.SniGetConnectionId(Handle, ref clientConnectionId); + + internal override uint DisableSsl() + => SniNativeWrapper.SniRemoveProvider(Handle, Provider.SSL_PROV); + + internal override uint EnableMars(ref uint info) + => SniNativeWrapper.SniAddProvider(Handle, Provider.SMUX_PROV, ref info); + + internal override uint SetConnectionBufferSize(ref uint unsignedPacketSize) + => SniNativeWrapper.SniSetInfo(Handle, QueryType.SNI_QUERY_CONN_BUFSIZE, ref unsignedPacketSize); + + internal override void DisposePacketCache() + { + lock (_writePacketLockObject) + { +#if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); +#endif + try + { } + finally + { + _writePacketCache.Dispose(); + // Do not set _writePacketCache to null, just in case a WriteAsyncCallback completes after this point + } + } + } + + internal override SspiContextProvider CreateSspiContextProvider() => new NativeSspiContextProvider(); + } +} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/assemblycache.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/assemblycache.cs deleted file mode 100644 index d8a877073a..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/assemblycache.cs +++ /dev/null @@ -1,64 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -//------------------------------------------------------------------------------ -//ThisAssembly class keeps a map of the following: -//AssemblyName to AssemblyID -//TypeName to TypeID -//AssemblyID to AssemblyRef and State -//TypeID to TypeRef and AssemblyId -// -//Adding an assembly to this class will NOT enable users to create types from that assembly. Users should explicitly add type details and link types to assemblies. -// This class also registers for assembly resolve events so that dependent assemblies can be resolved if they are registered. -//This class does NOT know anything about assembly dependencies. It simply loads assemblies as handed over to it. -//Users can take advantage of connection pooling by tying this instance to a pooling-aware component. -//------------------------------------------------------------------------------ - -using System; -using System.Diagnostics; -using Microsoft.Data.SqlClient.Server; - -namespace Microsoft.Data.SqlClient -{ - - internal sealed class AssemblyCache - { - private AssemblyCache() - { /* prevent utility class from being instantiated*/ - } - - internal static int GetLength(Object inst) - { - //caller should have allocated enough, based on MaxByteSize - return SerializationHelperSql9.SizeInBytes(inst); - } - - //The attribute we are looking for is now moved to an external dll that server provides. If the name is changed. - //then we we have to make corresponding changes here. - //please also change sqludcdatetime.cs, sqltime.cs and sqldate.cs - - internal static SqlUdtInfo GetInfoFromType(Type t) - { - Debug.Assert(t != null, "Type object cant be NULL"); - - Type orig = t; - do - { - SqlUdtInfo attr = SqlUdtInfo.TryGetFromType(t); - - if (attr != null) - { - return attr; - } - else - { - t = t.BaseType; - } - } - while (t != null); - - throw SQL.UDTInvalidSqlType(orig.AssemblyQualifiedName); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlTypes/SqlStreamChars.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlTypes/SqlStreamChars.cs deleted file mode 100644 index 5067e9eb84..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlTypes/SqlStreamChars.cs +++ /dev/null @@ -1,190 +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. - -namespace Microsoft.Data.SqlTypes -{ - using System; - using System.Data.SqlTypes; - using System.IO; - - internal abstract class SqlStreamChars : System.Data.SqlTypes.INullable, IDisposable - { - public abstract bool IsNull { get; } - - public abstract bool CanRead { get; } - - public abstract bool CanSeek { get; } - - public abstract bool CanWrite { get; } - - public abstract long Length { get; } - - public abstract long Position { get; set; } - - // -------------------------------------------------------------- - // Public methods - // -------------------------------------------------------------- - public abstract int Read(char[] buffer, int offset, int count); - - public abstract void Write(char[] buffer, int offset, int count); - - public abstract long Seek(long offset, SeekOrigin origin); - - public abstract void SetLength(long value); - - public abstract void Flush(); - - public virtual void Close() - { - Dispose(true); - } - - void IDisposable.Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - } - - public virtual int ReadChar() - { - // Reads one char from the stream by calling Read(char[], int, int). - // Will return an char cast to an int or -1 on end of stream. - // The performance of the default implementation on Stream is bad, - // and any subclass with an internal buffer should override this method. - char[] oneCharArray = new char[1]; - int r = Read(oneCharArray, 0, 1); - if (r == 0) - return -1; - return oneCharArray[0]; - } - - public virtual void WriteChar(char value) - { - // Writes one char from the stream by calling Write(char[], int, int). - // The performance of the default implementation on Stream is bad, - // and any subclass with an internal buffer should override this method. - char[] oneCharArray = new char[1]; - oneCharArray[0] = value; - Write(oneCharArray, 0, 1); - } - - - // Private class: the Null SqlStreamChars - private class NullSqlStreamChars : SqlStreamChars - { - // -------------------------------------------------------------- - // Constructor(s) - // -------------------------------------------------------------- - - internal NullSqlStreamChars() - { - } - - - // -------------------------------------------------------------- - // Public properties - // -------------------------------------------------------------- - - public override bool IsNull - { - get - { - return true; - } - } - - public override bool CanRead - { - get - { - return false; - } - } - - public override bool CanSeek - { - get - { - return false; - } - } - - public override bool CanWrite - { - get - { - return false; - } - } - - public override long Length - { - get - { - throw new SqlNullValueException(); - } - } - - public override long Position - { - get - { - throw new SqlNullValueException(); - } - set - { - throw new SqlNullValueException(); - } - } - - // -------------------------------------------------------------- - // Public methods - // -------------------------------------------------------------- - public override int Read(char[] buffer, int offset, int count) - { - throw new SqlNullValueException(); - } - - public override void Write(char[] buffer, int offset, int count) - { - throw new SqlNullValueException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new SqlNullValueException(); - } - - public override void SetLength(long value) - { - throw new SqlNullValueException(); - } - - public override void Flush() - { - throw new SqlNullValueException(); - } - - public override void Close() - { - } - - } // class NullSqlStreamChars - - - // The Null instance - public static SqlStreamChars Null - { - get - { - return new NullSqlStreamChars(); - } - } - - } // class SqlStreamChars - -} // namespace Microsoft.Data.SqlTypes diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.netfx.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.netfx.cs index 941e3325b6..af7269ed8b 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.netfx.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlTypes/SqlTypeWorkarounds.netfx.cs @@ -5,8 +5,6 @@ using System; using System.Data.SqlTypes; using System.Reflection; -using System.Reflection.Emit; -using System.Runtime.Serialization; using Microsoft.Data.SqlClient; namespace Microsoft.Data.SqlTypes @@ -20,7 +18,10 @@ namespace Microsoft.Data.SqlTypes internal static partial class SqlTypeWorkarounds { #region Work around inability to access SqlMoney.ctor(long, int) and SqlMoney.ToSqlInternalRepresentation - private static readonly Func s_sqlMoneyfactory = CtorHelper.CreateFactory(); // binds to SqlMoney..ctor(long, int) if it exists + // Documentation for internal ctor: + // https://learn.microsoft.com/en-us/dotnet/framework/additional-apis/system.data.sqltypes.sqlmoney.-ctor + private static readonly Func s_sqlMoneyfactory = + CtorHelper.CreateFactory(); // binds to SqlMoney..ctor(long, int) if it exists /// /// Constructs a SqlMoney from a long value without scaling. The ignored parameter exists @@ -70,6 +71,11 @@ internal static SqlMoneyToLongDelegate GetSqlMoneyToLong() private static SqlMoneyToLongDelegate GetFastSqlMoneyToLong() { + // Note: Although it would be faster to use the m_value member variable in + // SqlMoney, but because it is not documented, we cannot use it. The method + // we are calling below *is* documented, despite it being internal. + // Documentation for internal method: + // https://learn.microsoft.com/en-us/dotnet/framework/additional-apis/system.data.sqltypes.sqlmoney.tosqlinternalrepresentation MethodInfo toSqlInternalRepresentation = typeof(SqlMoney).GetMethod("ToSqlInternalRepresentation", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.ExactBinding, null, CallingConventions.Any, new Type[] { }, null); @@ -113,145 +119,45 @@ private static long FallbackSqlMoneyToLong(ref SqlMoney value) } #endregion - #region Work around inability to access SqlDecimal._data1/2/3/4 - internal static void SqlDecimalExtractData(SqlDecimal d, out uint data1, out uint data2, out uint data3, out uint data4) - { - SqlDecimalHelper.s_decompose(d, out data1, out data2, out data3, out data4); - } + #region Work around SqlDecimal.WriteTdsValue not existing in netfx - private static class SqlDecimalHelper + /// + /// Implementation that mimics netcore's WriteTdsValue method. + /// + /// + /// Although calls to this method could just be replaced with calls to + /// , using this mimic method allows netfx and netcore + /// implementations to be more cleanly switched. + /// + /// SqlDecimal value to get data from. + /// First data field will be written here. + /// Second data field will be written here. + /// Third data field will be written here. + /// Fourth data field will be written here. + internal static void SqlDecimalExtractData( + SqlDecimal value, + out uint data1, + out uint data2, + out uint data3, + out uint data4) { - internal delegate void Decomposer(SqlDecimal value, out uint data1, out uint data2, out uint data3, out uint data4); - internal static readonly Decomposer s_decompose = GetDecomposer(); - - private static Decomposer GetDecomposer() - { - Decomposer decomposer = null; - try - { - decomposer = GetFastDecomposer(); - } - catch - { - // If an exception occurs for any reason, swallow & use the fallback code path. - } - - return decomposer ?? FallbackDecomposer; - } - - private static Decomposer GetFastDecomposer() - { - // This takes advantage of the fact that for [Serializable] types, the member fields are implicitly - // part of the type's serialization contract. This includes the fields' names and types. By default, - // [Serializable]-compliant serializers will read all the member fields and shove the data into a - // SerializationInfo dictionary. We mimic this behavior in a manner consistent with the [Serializable] - // pattern, but much more efficiently. - // - // In order to make sure we're staying compliant, we need to gate our checks to fulfill some core - // assumptions. Importantly, the type must be [Serializable] but cannot be ISerializable, as the - // presence of the interface means that the type wants to be responsible for its own serialization, - // and that member fields are not guaranteed to be part of the serialization contract. Additionally, - // we need to check for [OnSerializing] and [OnDeserializing] methods, because we cannot account - // for any logic which might be present within them. - - if (!typeof(SqlDecimal).IsSerializable) - { - SqlClientEventSource.Log.TryTraceEvent("SqlTypeWorkarounds.SqlDecimalHelper.GetFastDecomposer | Info | SqlDecimal isn't Serializable. Less efficient fallback method will be used."); - return null; // type is not serializable - cannot use fast path assumptions - } - - if (typeof(ISerializable).IsAssignableFrom(typeof(SqlDecimal))) - { - SqlClientEventSource.Log.TryTraceEvent("SqlTypeWorkarounds.SqlDecimalHelper.GetFastDecomposer | Info | SqlDecimal is ISerializable. Less efficient fallback method will be used."); - return null; // type contains custom logic - cannot use fast path assumptions - } - - foreach (MethodInfo method in typeof(SqlDecimal).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) - { - if (method.IsDefined(typeof(OnDeserializingAttribute)) || method.IsDefined(typeof(OnDeserializedAttribute))) - { - SqlClientEventSource.Log.TryTraceEvent("SqlTypeWorkarounds.SqlDecimalHelper.GetFastDecomposer | Info | SqlDecimal contains custom serialization logic. Less efficient fallback method will be used."); - return null; // type contains custom logic - cannot use fast path assumptions - } - } - - // GetSerializableMembers filters out [NonSerialized] fields for us automatically. - - FieldInfo fiData1 = null, fiData2 = null, fiData3 = null, fiData4 = null; - foreach (MemberInfo candidate in FormatterServices.GetSerializableMembers(typeof(SqlDecimal))) - { - if (candidate is FieldInfo fi && fi.FieldType == typeof(uint)) - { - if (fi.Name == "m_data1") - { fiData1 = fi; } - else if (fi.Name == "m_data2") - { fiData2 = fi; } - else if (fi.Name == "m_data3") - { fiData3 = fi; } - else if (fi.Name == "m_data4") - { fiData4 = fi; } - } - } - - if (fiData1 is null || fiData2 is null || fiData3 is null || fiData4 is null) - { - SqlClientEventSource.Log.TryTraceEvent("SqlTypeWorkarounds.SqlDecimalHelper.GetFastDecomposer | Info | Expected SqlDecimal fields are missing. Less efficient fallback method will be used."); - return null; // missing one of the expected member fields - cannot use fast path assumptions - } - - Type refToUInt32 = typeof(uint).MakeByRefType(); - DynamicMethod dm = new( - name: "sqldecimal-decomposer", - returnType: typeof(void), - parameterTypes: new[] { typeof(SqlDecimal), refToUInt32, refToUInt32, refToUInt32, refToUInt32 }, - restrictedSkipVisibility: true); // perf: JITs method at delegate creation time - - ILGenerator ilGen = dm.GetILGenerator(); - ilGen.Emit(OpCodes.Ldarg_1); // eval stack := [UInt32&] - ilGen.Emit(OpCodes.Ldarg_0); // eval stack := [UInt32&] [SqlDecimal] - ilGen.Emit(OpCodes.Ldfld, fiData1); // eval stack := [UInt32&] [UInt32] - ilGen.Emit(OpCodes.Stind_I4); // eval stack := - ilGen.Emit(OpCodes.Ldarg_2); // eval stack := [UInt32&] - ilGen.Emit(OpCodes.Ldarg_0); // eval stack := [UInt32&] [SqlDecimal] - ilGen.Emit(OpCodes.Ldfld, fiData2); // eval stack := [UInt32&] [UInt32] - ilGen.Emit(OpCodes.Stind_I4); // eval stack := - ilGen.Emit(OpCodes.Ldarg_3); // eval stack := [UInt32&] - ilGen.Emit(OpCodes.Ldarg_0); // eval stack := [UInt32&] [SqlDecimal] - ilGen.Emit(OpCodes.Ldfld, fiData3); // eval stack := [UInt32&] [UInt32] - ilGen.Emit(OpCodes.Stind_I4); // eval stack := - ilGen.Emit(OpCodes.Ldarg_S, (byte)4); // eval stack := [UInt32&] - ilGen.Emit(OpCodes.Ldarg_0); // eval stack := [UInt32&] [SqlDecimal] - ilGen.Emit(OpCodes.Ldfld, fiData4); // eval stack := [UInt32&] [UInt32] - ilGen.Emit(OpCodes.Stind_I4); // eval stack := - ilGen.Emit(OpCodes.Ret); - - return (Decomposer)dm.CreateDelegate(typeof(Decomposer), null /* target */); - } - - // Used in case we can't use a [Serializable]-like mechanism. - private static void FallbackDecomposer(SqlDecimal value, out uint data1, out uint data2, out uint data3, out uint data4) - { - if (value.IsNull) - { - data1 = default; - data2 = default; - data3 = default; - data4 = default; - } - else - { - int[] data = value.Data; // allocation - data4 = (uint)data[3]; // write in reverse to avoid multiple bounds checks - data3 = (uint)data[2]; - data2 = (uint)data[1]; - data1 = (uint)data[0]; - } - } + // Note: Although it would be faster to use the m_data[1-4] member variables in + // SqlDecimal, we cannot use them because they are not documented. The Data property + // is less ideal, but is documented. + int[] data = value.Data; + data1 = (uint)data[0]; + data2 = (uint)data[1]; + data3 = (uint)data[2]; + data4 = (uint)data[3]; } + #endregion #region Work around inability to access SqlBinary.ctor(byte[], bool) - private static readonly Func s_sqlBinaryfactory = CtorHelper.CreateFactory(); // binds to SqlBinary..ctor(byte[], bool) if it exists + // Documentation of internal constructor: + // https://learn.microsoft.com/en-us/dotnet/framework/additional-apis/system.data.sqltypes.sqlbinary.-ctor + private static readonly Func s_sqlBinaryfactory = + CtorHelper.CreateFactory(); internal static SqlBinary SqlBinaryCtor(byte[] value, bool ignored) { @@ -270,7 +176,10 @@ internal static SqlBinary SqlBinaryCtor(byte[] value, bool ignored) #endregion #region Work around inability to access SqlGuid.ctor(byte[], bool) - private static readonly Func s_sqlGuidfactory = CtorHelper.CreateFactory(); // binds to SqlGuid..ctor(byte[], bool) if it exists + // Documentation for internal constructor: + // https://learn.microsoft.com/en-us/dotnet/framework/additional-apis/system.data.sqltypes.sqlguid.-ctor + private static readonly Func s_sqlGuidfactory = + CtorHelper.CreateFactory(); internal static SqlGuid SqlGuidCtor(byte[] value, bool ignored) { diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml b/src/Microsoft.Data.SqlClient/netfx/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml deleted file mode 100644 index 73ddc2f575..0000000000 --- a/src/Microsoft.Data.SqlClient/netfx/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml +++ /dev/null @@ -1,2539 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - MetaDataCollections - 0 - 0 - DataTable - MetaDataCollections - - - DataSourceInformation - 0 - 0 - DataTable - DataSourceInformation - - - DataTypes - 0 - 0 - PrepareCollection - - - Restrictions - 0 - 0 - DataTable - Restrictions - - - ReservedWords - 0 - 0 - DataTable - ReservedWords - - - Users - 1 - 1 - SQLCommand - select uid, name as user_name, createdate, updatedate from sysusers where (name = @Name or (@Name is null)) - - - Databases - 1 - 1 - SQLCommand - select name as database_name, dbid, crdate as create_date from master..sysdatabases where (name = @Name or (@Name is null)) - 09.99.999.9 - - - Databases - 1 - 1 - SQLCommand - IF OBJECT_ID('master..sysdatabases') IS NULL EXEC sp_executesql N'select name as database_name, dbid, crdate as create_date from sysdatabases where (name = @Name or (@Name is null))',N'@Name NVARCHAR(128)',@Name=@Name ELSE EXEC sp_executesql N'select name as database_name, dbid, crdate as create_date from master..sysdatabases where (name = @Name or (@Name is null))',N'@Name NVARCHAR(128)',@Name=@Name - 10.00.000.0 - - - Tables - 4 - 3 - SQLCommand - select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE from INFORMATION_SCHEMA.TABLES where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Name or (@Name is null)) and (TABLE_TYPE = @TableType or (@TableType is null)) - - - Columns - 4 - 4 - SQLCommand - select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, CHARACTER_SET_CATALOG, CHARACTER_SET_SCHEMA, CHARACTER_SET_NAME, COLLATION_CATALOG from INFORMATION_SCHEMA.COLUMNS where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) and (COLUMN_NAME = @Column or (@Column is null)) order by TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME - 09.99.999.9 - - - Columns - 4 - 4 - SQLCommand - EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, @Column, 0 - 10.00.000.0 - - - AllColumns - 4 - 4 - SQLCommand - EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, @Column, 1 - 10.00.000.0 - - - ColumnSetColumns - 3 - 3 - SQLCommand - EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, null, 2 - 10.00.000.0 - - - StructuredTypeMembers - 4 - 4 - SQLCommand - SELECT DB_NAME() AS TYPE_CATALOG, sc.name AS TYPE_SCHEMA, tt.name AS TYPE_NAME, c.name AS MEMBER_NAME, ColumnProperty(c.object_id, c.name, 'ordinal') AS ORDINAL_POSITION, convert(nvarchar(4000), object_definition(c.default_object_id)) AS MEMBER_DEFAULT, convert(varchar(3), CASE c.is_nullable WHEN 1 THEN 'YES' ELSE 'NO' END) AS IS_NULLABLE, type_name(c.system_type_id) AS DATA_TYPE, ColumnProperty(c.object_id, c.name, 'charmaxlen') AS CHARACTER_MAXIMUM_LENGTH, ColumnProperty(c.object_id, c.name, 'octetmaxlen') AS CHARACTER_OCTET_LENGTH, convert(tinyint, CASE WHEN c.system_type_id IN /* int/decimal/numeric/real/float/money */ (48, 52, 56, 59, 60, 62, 106, 108, 122, 127) THEN c.precision END) AS NUMERIC_PRECISION, convert(smallint, CASE WHEN c.system_type_id IN /* int/money/decimal/numeric */ (48, 52, 56, 60, 106, 108, 122, 127) THEN 10 WHEN c.system_type_id IN /* real/float */ (59, 62) THEN 2 END) AS NUMERIC_PRECISION_RADIX, convert(int, CASE WHEN c.system_type_id IN /* datetime/smalldatetime */ (58, 61) THEN NULL ELSE odbcscale(c.system_type_id, c.scale) END) AS NUMERIC_SCALE, convert(smallint, CASE WHEN c.system_type_id IN /* datetime/smalldatetime */ (58, 61) THEN 3 END) AS DATETIME_PRECISION, convert(sysname, null) AS CHARACTER_SET_CATALOG, convert(sysname, null) AS CHARACTER_SET_SCHEMA, convert(sysname, CASE WHEN c.system_type_id IN /* char/varchar/text */ (35, 167, 175) THEN CollationProperty(c.collation_name, 'sqlcharsetname') WHEN c.system_type_id IN /* nchar/nvarchar/ntext */ (99, 231, 239) THEN N'UNICODE' END) AS CHARACTER_SET_NAME, convert(sysname, null) AS COLLATION_CATALOG FROM sys.table_types tt join sys.objects o on o.object_id = tt.type_table_object_id JOIN sys.schemas sc on sc.schema_id = tt.schema_id JOIN sys.columns c ON c.object_id = o.object_id LEFT JOIN sys.types t ON c.user_type_id = t.user_type_id WHERE o.type IN ('TT') AND (DB_NAME() = @Catalog or (@Catalog is null)) and (sc.name = @Owner or (@Owner is null)) and (tt.name = @Type or (@Type is null)) and (c.name = @Member or (@Member is null)) order by sc.name, tt.name, c.name - 09.00.000.0 - - - Views - 3 - 3 - SQLCommand - select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, CHECK_OPTION, IS_UPDATABLE from INFORMATION_SCHEMA.VIEWS where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) order by TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME - 08.00.000.0 - - - ViewColumns - 4 - 4 - SQLCommand - select VIEW_CATALOG, VIEW_SCHEMA, VIEW_NAME, TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME from INFORMATION_SCHEMA.VIEW_COLUMN_USAGE where (VIEW_CATALOG = @Catalog or (@Catalog is null)) and (VIEW_SCHEMA = @Owner or (@Owner is null)) and (VIEW_NAME = @Table or (@Table is null)) and (COLUMN_NAME = @Column or (@Column is null)) order by VIEW_CATALOG, VIEW_SCHEMA, VIEW_NAME - 08.00.000.0 - - - ProcedureParameters - 4 - 1 - SQLCommand - select SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, ORDINAL_POSITION, PARAMETER_MODE, IS_RESULT, AS_LOCATOR, PARAMETER_NAME, CASE WHEN DATA_TYPE IS NULL THEN USER_DEFINED_TYPE_NAME WHEN DATA_TYPE = 'table type' THEN USER_DEFINED_TYPE_NAME ELSE DATA_TYPE END as DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH, COLLATION_CATALOG, COLLATION_SCHEMA, COLLATION_NAME, CHARACTER_SET_CATALOG, CHARACTER_SET_SCHEMA, CHARACTER_SET_NAME, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, INTERVAL_TYPE, INTERVAL_PRECISION from INFORMATION_SCHEMA.PARAMETERS where (SPECIFIC_CATALOG = @Catalog or (@Catalog is null)) and (SPECIFIC_SCHEMA = @Owner or (@Owner is null)) and (SPECIFIC_NAME = @Name or (@Name is null)) and (PARAMETER_NAME = @Parameter or (@Parameter is null)) order by SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, PARAMETER_NAME - 08.00.0000 - - - Procedures - 4 - 3 - SQLCommand - select SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, ROUTINE_CATALOG, ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE, CREATED, LAST_ALTERED from INFORMATION_SCHEMA.ROUTINES where (SPECIFIC_CATALOG = @Catalog or (@Catalog is null)) and (SPECIFIC_SCHEMA = @Owner or (@Owner is null)) and (SPECIFIC_NAME = @Name or (@Name is null)) and (ROUTINE_TYPE = @Type or (@Type is null)) order by SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME - 08.00.0000 - - - ForeignKeys - 4 - 3 - SQLCommand - select CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME, TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, CONSTRAINT_TYPE, IS_DEFERRABLE, INITIALLY_DEFERRED from INFORMATION_SCHEMA.TABLE_CONSTRAINTS where (CONSTRAINT_CATALOG = @Catalog or (@Catalog is null)) and (CONSTRAINT_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) and (CONSTRAINT_NAME = @Name or (@Name is null)) and CONSTRAINT_TYPE = 'FOREIGN KEY' order by CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME - - - IndexColumns - 5 - 4 - SQLCommand - select distinct db_Name() as constraint_catalog, constraint_schema = user_name(o.uid), constraint_name = x.name, table_catalog = db_name(), table_schema = user_name(o.uid), table_name = o.name, column_name = c.name, ordinal_position = convert(int, xk.keyno), KeyType = c.xtype, index_name = x.name from sysobjects o, sysindexes x, syscolumns c, sysindexkeys xk where o.type in ('U') and x.id = o.id and o.id = c.id and o.id = xk.id and x.indid = xk.indid and c.colid = xk.colid and xk.keyno < = x.keycnt and permissions(o.id, c.name) <> 0 and (db_name() = @Catalog or (@Catalog is null)) and (user_name()= @Owner or (@Owner is null)) and (o.name = @Table or (@Table is null)) and (x.name = @ConstraintName or (@ConstraintName is null)) and (c.name = @Column or (@Column is null)) order by table_name, index_name - 09.99.9999 - - - IndexColumns - 5 - 4 - SQLCommand - EXEC sys.sp_indexcolumns_managed @Catalog, @Owner, @Table, @ConstraintName, @Column - 10.00.0000 - - - Indexes - 4 - 3 - SQLCommand - select distinct db_Name() as constraint_catalog, constraint_schema = user_name(o.uid), constraint_name = x.name, table_catalog = db_name(), table_schema = user_name(o.uid), table_name = o.name, index_name = x.name from sysobjects o, sysindexes x, sysindexkeys xk where o.type in ('U') and x.id = o.id and o.id = xk.id and x.indid = xk.indid and xk.keyno < = x.keycnt and (db_name() = @Catalog or (@Catalog is null)) and (user_name()= @Owner or (@Owner is null)) and (o.name = @Table or (@Table is null)) and (x.name = @Name or (@Name is null)) order by table_name, index_name - 09.99.9999 - - - Indexes - 4 - 3 - SQLCommand - EXEC sys.sp_indexes_managed @Catalog, @Owner, @Table, @Name - 10.00.0000 - - - UserDefinedTypes - 2 - 1 - SQLCommand - select assemblies.name as assembly_name, types.assembly_class as udt_name, ASSEMBLYPROPERTY(assemblies.name, 'VersionMajor') as version_major, ASSEMBLYPROPERTY(assemblies.name, 'VersionMinor') as version_minor, ASSEMBLYPROPERTY(assemblies.name, 'VersionBuild') as version_build, ASSEMBLYPROPERTY(assemblies.name, 'VersionRevision') as version_revision, ASSEMBLYPROPERTY(assemblies.name, 'CultureInfo') as culture_info, ASSEMBLYPROPERTY(assemblies.name, 'PublicKey') as public_key, is_fixed_length, max_length, Create_Date, Permission_set_desc from sys.assemblies as assemblies join sys.assembly_types as types on assemblies.assembly_id = types.assembly_id where (assemblies.name = @AssemblyName or (@AssemblyName is null)) and (types.assembly_class = @UDTName or (@UDTName is null)) - 09.00.0000 - - - Users - User_Name - @Name - name - 1 - - - Databases - Name - @Name - Name - 1 - - - Tables - Catalog - @Catalog - TABLE_CATALOG - 1 - - - Tables - Owner - @Owner - TABLE_SCHEMA - 2 - - - Tables - Table - @Name - TABLE_NAME - 3 - - - Tables - TableType - @TableType - TABLE_TYPE - 4 - - - Columns - Catalog - @Catalog - TABLE_CATALOG - 1 - - - Columns - Owner - @Owner - TABLE_SCHEMA - 2 - - - Columns - Table - @Table - TABLE_NAME - 3 - - - Columns - Column - @Column - COLUMN_NAME - 4 - - - AllColumns - Catalog - @Catalog - TABLE_CATALOG - 1 - - - AllColumns - Owner - @Owner - TABLE_SCHEMA - 2 - - - AllColumns - Table - @Table - TABLE_NAME - 3 - - - AllColumns - Column - @Column - COLUMN_NAME - 4 - - - ColumnSetColumns - Catalog - @Catalog - TABLE_CATALOG - 1 - - - ColumnSetColumns - Owner - @Owner - TABLE_SCHEMA - 2 - - - ColumnSetColumns - Table - @Table - TABLE_NAME - 3 - - - StructuredTypeMembers - Catalog - @Catalog - TYPE_CATALOG - 1 - - - StructuredTypeMembers - Owner - @Owner - TYPE_SCHEMA - 2 - - - StructuredTypeMembers - Type - @Type - TYPE_NAME - 3 - - - StructuredTypeMembers - Member - @Member - MEMBER_NAME - 4 - - - Views - Catalog - @Catalog - TABLE_CATALOG - 1 - 08.00.000.0 - - - Views - Owner - @Owner - TABLE_SCHEMA - 2 - 08.00.000.0 - - - Views - Table - @Table - TABLE_NAME - 3 - 08.00.000.0 - - - ViewColumns - Catalog - @Catalog - VIEW_CATALOG - 1 - 08.00.000.0 - - - ViewColumns - Owner - @Owner - VIEW_SCHEMA - 2 - 08.00.000.0 - - - ViewColumns - Table - @Table - VIEW_NAME - 3 - 08.00.000.0 - - - ViewColumns - Column - @Column - COLUMN_NAME - 4 - 08.00.000.0 - - - ProcedureParameters - Catalog - @Catalog - SPECIFIC_CATALOG - 1 - 08.00.0000 - - - ProcedureParameters - Owner - @Owner - SPECIFIC_SCHEMA - 2 - 08.00.0000 - - - ProcedureParameters - Name - @Name - SPECIFIC_NAME - 3 - 08.00.0000 - - - ProcedureParameters - Parameter - @Parameter - PARAMETER_NAME - 4 - 08.00.0000 - - - Procedures - Catalog - @Catalog - SPECIFIC_CATALOG - 1 - 08.00.0000 - - - Procedures - Owner - @Owner - SPECIFIC_SCHEMA - 2 - 08.00.0000 - - - Procedures - Name - @Name - SPECIFIC_NAME - 3 - 08.00.0000 - - - Procedures - Type - @Type - ROUTINE_TYPE - 4 - 08.00.0000 - - - IndexColumns - Catalog - @Catalog - db_name() - 1 - - - IndexColumns - Owner - @Owner - user_name() - 2 - - - IndexColumns - Table - @Table - o.name - 3 - - - IndexColumns - ConstraintName - @ConstraintName - x.name - 4 - - - IndexColumns - Column - @Column - c.name - 5 - - - Indexes - Catalog - @Catalog - db_name() - 1 - - - Indexes - Owner - @Owner - user_name() - 2 - - - Indexes - Table - @Table - o.name - 3 - - - Indexes - Name - @Name - x.name - 4 - - - UserDefinedTypes - assembly_name - @AssemblyName - assemblies.name - 1 - 09.00.000.0 - - - UserDefinedTypes - udt_name - @UDTName - types.assembly_class - 2 - 09.00.000.0 - - - ForeignKeys - Catalog - @Catalog - CONSTRAINT_CATALOG - 1 - - - ForeignKeys - Owner - @Owner - CONSTRAINT_SCHEMA - 2 - - - ForeignKeys - Table - @Table - TABLE_NAME - 3 - - - ForeignKeys - Name - @Name - CONSTRAINT_NAME - 4 - - - \. - Microsoft SQL Server - 2 - (^\[\p{Lo}\p{Lu}\p{Ll}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Nd}@$#_]*$)|(^\[[^\]\0]|\]\]+\]$)|(^\"[^\"\0]|\"\"+\"$) - 1 - false - {0} - @[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$) - 128 - ^[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$) - (([^\[]|\]\])*) - 1 - ; - '(([^']|'')*)' - 15 - - -smallint -16 -5 -smallint -System.Int16 -true -true -false -false -true -true -false -true -true -false -false - - -int -8 -10 -int -System.Int32 -true -true -false -false -true -true -false -true -true -false -false - - -real -13 -7 -real -System.Single -false -true -false -false -true -false -false -true -true -false -false - - -float -6 -53 -float({0}) -number of bits used to store the mantissa -System.Double -false -true -false -false -true -false -false -true -true -false -false - - -money -9 -19 -money -System.Decimal -false -false -false -false -true -true -false -true -true -false -false - - -smallmoney -17 -10 -smallmoney -System.Decimal -false -false -false -false -true -true -false -true -true -false -false - - -bit -2 -1 -bit -System.Boolean -false -false -false -false -true -false -false -true -true -false - - -tinyint -20 -3 -tinyint -System.Byte -true -true -false -false -true -true -false -true -true -false -true - - -bigint -0 -19 -bigint -System.Int64 -true -true -false -false -true -true -false -true -true -false -false - - -varbinary -21 -8000 -varbinary({0}) -max length -System.Byte[] -false -true -false -false -false -false -false -true -true -false -08.99.999.9 -0x - - -timestamp -19 -8 -timestamp -System.Byte[] -false -false -false -true -true -false -false -false -true -false -0x - - -binary -1 -8000 -binary({0}) -length -System.Byte[] -false -true -false -false -true -false -false -true -true -false -0x - - -image -7 -2147483647 -image -System.Byte[] -false -true -false -false -false -false -true -true -false -false -0x - - -char -3 -8000 -char -length -System.String -false -true -false -false -true -false -false -true -true -true -08.99.999.9 -' -' - - -text -18 -2147483647 -text -System.String -false -true -false -false -false -false -true -true -false -true -' -' - - -varchar -22 -8000 -varchar({0}) -max length -System.String -false -true -false -false -false -false -false -true -true -true -08.99.999.9 -' -' - - -nchar -10 -4000 -nchar({0}) -length -System.String -false -true -false -false -true -false -false -true -true -true -08.99.999.9 -N' -' - - -ntext -11 -1073741823 -ntext -System.String -false -true -false -false -false -false -true -true -false -true -N' -' - - -nvarchar -12 -4000 -nvarchar({0}) -max length -System.String -false -true -false -false -false -false -false -true -true -true -08.99.999.9 -N' -' - - -decimal -5 -38 -decimal({0}, {1}) -precision,scale -System.Decimal -true -true -false -false -true -false -false -true -true -false -false -38 -0 - - -numeric -5 -38 -numeric({0}, {1}) -precision,scale -System.Decimal -true -true -false -false -true -false -false -true -true -false -false -38 -0 - - -datetime -4 -23 -datetime -System.DateTime -false -true -false -false -true -false -false -true -true -true -{ts ' -'} - - -smalldatetime -15 -16 -smalldatetime -System.DateTime -false -true -false -false -true -false -false -true -true -true -{ts ' -'} - - -sql_variant -23 -sql_variant -System.Object -false -true -false -false -false -false -false -true -true -false -false - - -xml -25 -2147483647 -xml -System.String -false -false -false -false -false -false -true -true -false -false -09.00.000.0 -false - - -varchar -22 -2147483647 -varchar({0}) -max length -System.String -false -true -false -false -false -false -false -true -true -true -09.00.000.0 -' -' - - -char -3 -2147483647 -char({0}) -length -System.String -false -true -false -false -true -false -false -true -true -true -09.00.000.0 -' -' - - -nchar -10 -1073741823 -nchar({0}) -length -System.String -false -true -false -false -true -false -false -true -true -true -09.00.000.0 -N' -' - - -nvarchar -12 -1073741823 -nvarchar({0}) -max length -System.String -false -true -false -false -false -false -false -true -true -true -09.00.000.0 -N' -' - - -varbinary -21 -1073741823 -varbinary({0}) -max length -System.Byte[] -false -true -false -false -false -false -false -true -true -false -09.00.000.0 -0x - - -uniqueidentifier -14 -16 -uniqueidentifier -System.Guid -false -true -false -false -true -false -false -true -true -false -' -' - - -date -31 -3 -date -System.DateTime -false -false -false -false -true -true -false -true -true -true -10.00.000.0 -{ts ' -'} - - -time -32 -5 -time({0}) -scale -System.TimeSpan -false -false -false -false -false -false -false -true -true -true -7 -0 -10.00.000.0 -{ts ' -'} - - -datetime2 -33 -8 -datetime2({0}) -scale -System.DateTime -false -true -false -false -false -false -false -true -true -true -7 -0 -10.00.000.0 -{ts ' -'} - - -datetimeoffset -34 -10 -datetimeoffset({0}) -scale -System.DateTimeOffset -false -true -false -false -false -false -false -true -true -true -7 -0 -10.00.000.0 -{ts ' -'} - - - ADD - - - EXCEPT - - - PERCENT - - - ALL - - - EXEC - - - PLAN - - - ALTER - - - EXECUTE - - - PRECISION - - - AND - - - EXISTS - - - PRIMARY - - - ANY - - - EXIT - - - PRINT - - - AS - - - FETCH - - - PROC - - - ASC - - - FILE - - - PROCEDURE - - - AUTHORIZATION - - - FILLFACTOR - - - PUBLIC - - - BACKUP - - - FOR - - - RAISERROR - - - BEGIN - - - FOREIGN - - - READ - - - BETWEEN - - - FREETEXT - - - READTEXT - - - BREAK - - - FREETEXTTABLE - - - RECONFIGURE - - - BROWSE - - - FROM - - - REFERENCES - - - BULK - - - FULL - - - REPLICATION - - - BY - - - FUNCTION - - - RESTORE - - - CASCADE - - - GOTO - - - RESTRICT - - - CASE - - - GRANT - - - RETURN - - - CHECK - - - GROUP - - - REVOKE - - - CHECKPOINT - - - HAVING - - - RIGHT - - - CLOSE - - - HOLDLOCK - - - ROLLBACK - - - CLUSTERED - - - IDENTITY - - - ROWCOUNT - - - COALESCE - - - IDENTITY_INSERT - - - ROWGUIDCOL - - - COLLATE - - - IDENTITYCOL - - - RULE - - - COLUMN - - - IF - - - SAVE - - - COMMIT - - - IN - - - SCHEMA - - - COMPUTE - - - INDEX - - - SELECT - - - CONSTRAINT - - - INNER - - - SESSION_USER - - - CONTAINS - - - INSERT - - - SET - - - CONTAINSTABLE - - - INTERSECT - - - SETUSER - - - CONTINUE - - - INTO - - - SHUTDOWN - - - CONVERT - - - IS - - - SOME - - - CREATE - - - JOIN - - - STATISTICS - - - CROSS - - - KEY - - - SYSTEM_USER - - - CURRENT - - - KILL - - - TABLE - - - CURRENT_DATE - - - LEFT - - - TEXTSIZE - - - CURRENT_TIME - - - LIKE - - - THEN - - - CURRENT_TIMESTAMP - - - LINENO - - - TO - - - CURRENT_USER - - - LOAD - - - TOP - - - CURSOR - - - NATIONAL - - - TRAN - - - DATABASE - - - NOCHECK - - - TRANSACTION - - - DBCC - - - NONCLUSTERED - - - TRIGGER - - - DEALLOCATE - - - NOT - - - TRUNCATE - - - DECLARE - - - NULL - - - TSEQUAL - - - DEFAULT - - - NULLIF - - - UNION - - - DELETE - - - OF - - - UNIQUE - - - DENY - - - OFF - - - UPDATE - - - DESC - - - OFFSETS - - - UPDATETEXT - - - DISK - - - ON - - - USE - - - DISTINCT - - - OPEN - - - USER - - - DISTRIBUTED - - - OPENDATASOURCE - - - VALUES - - - DOUBLE - - - OPENQUERY - - - VARYING - - - DROP - - - OPENROWSET - - - VIEW - - - DUMMY - - - OPENXML - - - WAITFOR - - - DUMP - - - OPTION - - - WHEN - - - ELSE - - - OR - - - WHERE - - - END - - - ORDER - - - WHILE - - - ERRLVL - - - OUTER - - - WITH - - - ESCAPE - - - OVER - - - WRITETEXT - - - ABSOLUTE - - - FOUND - - - PRESERVE - - - ACTION - - - FREE - - - PRIOR - - - ADMIN - - - GENERAL - - - PRIVILEGES - - - AFTER - - - GET - - - READS - - - AGGREGATE - - - GLOBAL - - - REAL - - - ALIAS - - - GO - - - RECURSIVE - - - ALLOCATE - - - GROUPING - - - REF - - - ARE - - - HOST - - - REFERENCING - - - ARRAY - - - HOUR - - - RELATIVE - - - ASSERTION - - - IGNORE - - - RESULT - - - AT - - - IMMEDIATE - - - RETURNS - - - BEFORE - - - INDICATOR - - - ROLE - - - BINARY - - - INITIALIZE - - - ROLLUP - - - BIT - - - INITIALLY - - - ROUTINE - - - BLOB - - - INOUT - - - ROW - - - BOOLEAN - - - INPUT - - - ROWS - - - BOTH - - - INT - - - SAVEPOINT - - - BREADTH - - - INTEGER - - - SCROLL - - - CALL - - - INTERVAL - - - SCOPE - - - CASCADED - - - ISOLATION - - - SEARCH - - - CAST - - - ITERATE - - - SECOND - - - CATALOG - - - LANGUAGE - - - SECTION - - - CHAR - - - LARGE - - - SEQUENCE - - - CHARACTER - - - LAST - - - SESSION - - - CLASS - - - LATERAL - - - SETS - - - CLOB - - - LEADING - - - SIZE - - - COLLATION - - - LESS - - - SMALLINT - - - COMPLETION - - - LEVEL - - - SPACE - - - CONNECT - - - LIMIT - - - SPECIFIC - - - CONNECTION - - - LOCAL - - - SPECIFICTYPE - - - CONSTRAINTS - - - LOCALTIME - - - SQL - - - CONSTRUCTOR - - - LOCALTIMESTAMP - - - SQLEXCEPTION - - - CORRESPONDING - - - LOCATOR - - - SQLSTATE - - - CUBE - - - MAP - - - SQLWARNING - - - CURRENT_PATH - - - MATCH - - - START - - - CURRENT_ROLE - - - MINUTE - - - STATE - - - CYCLE - - - MODIFIES - - - STATEMENT - - - DATA - - - MODIFY - - - STATIC - - - DATE - - - MODULE - - - STRUCTURE - - - DAY - - - MONTH - - - TEMPORARY - - - DEC - - - NAMES - - - TERMINATE - - - DECIMAL - - - NATURAL - - - THAN - - - DEFERRABLE - - - NCHAR - - - TIME - - - DEFERRED - - - NCLOB - - - TIMESTAMP - - - DEPTH - - - NEW - - - TIMEZONE_HOUR - - - DEREF - - - NEXT - - - TIMEZONE_MINUTE - - - DESCRIBE - - - NO - - - TRAILING - - - DESCRIPTOR - - - NONE - - - TRANSLATION - - - DESTROY - - - NUMERIC - - - TREAT - - - DESTRUCTOR - - - OBJECT - - - TRUE - - - DETERMINISTIC - - - OLD - - - UNDER - - - DICTIONARY - - - ONLY - - - UNKNOWN - - - DIAGNOSTICS - - - OPERATION - - - UNNEST - - - DISCONNECT - - - ORDINALITY - - - USAGE - - - DOMAIN - - - OUT - - - USING - - - DYNAMIC - - - OUTPUT - - - VALUE - - - EACH - - - PAD - - - VARCHAR - - - END-EXEC - - - PARAMETER - - - VARIABLE - - - EQUALS - - - PARAMETERS - - - WHENEVER - - - EVERY - - - PARTIAL - - - WITHOUT - - - EXCEPTION - - - PATH - - - WORK - - - EXTERNAL - - - POSTFIX - - - WRITE - - - FALSE - - - PREFIX - - - YEAR - - - FIRST - - - PREORDER - - - ZONE - - - FLOAT - - - PREPARE - - - ADA - - - AVG - - - BIT_LENGTH - - - CHAR_LENGTH - - - CHARACTER_LENGTH - - - COUNT - - - EXTRACT - - - FORTRAN - - - INCLUDE - - - INSENSITIVE - - - LOWER - - - MAX - - - MIN - - - OCTET_LENGTH - - - OVERLAPS - - - PASCAL - - - POSITION - - - SQLCA - - - SQLCODE - - - SQLERROR - - - SUBSTRING - - - SUM - - - TRANSLATE - - - TRIM - - - UPPER - - diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/ISniNativeMethods.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/ISniNativeMethods.cs new file mode 100644 index 0000000000..109b7c27ae --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/ISniNativeMethods.cs @@ -0,0 +1,101 @@ +// 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.Runtime.InteropServices; +using System.Text; +using Microsoft.Data.SqlClient; + +namespace Interop.Windows.Sni +{ + internal interface ISniNativeMethods + { + uint SniAddProvider(SNIHandle pConn, Provider provider, ref AuthProviderInfo pInfo); + + uint SniAddProvider(SNIHandle pConn, Provider provider, ref uint pInfo); + + uint SniCheckConnection(SNIHandle pConn); + + uint SniClose(IntPtr pConn); + + uint SniGetMaxComposedSpnLength(); + + uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out bool pbQueryInfo); + + uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out Guid pbQueryInfo); + + uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out Provider provider); + + uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out ushort portNumber); + + void SniGetLastError(out SniError pLastError); + + uint SniGetPeerAddrStrWrapper(SNIHandle pConn, int bufferSize, StringBuilder addrBuffer, out uint addrLength); + + uint SniInitialize(IntPtr pmo); + + uint SniIsTokenRestricted(IntPtr token, out bool isRestricted); + + uint SniOpenSyncExWrapper(ref SniClientConsumerInfo pClientConsumerInfo, out IntPtr ppConn); + + uint SniOpenWrapper( + ref SniConsumerInfo pConsumerInfo, + string connect, + SNIHandle pConn, + out IntPtr ppConn, + bool fSync, + SqlConnectionIPAddressPreference ipPreference, + ref SniDnsCacheInfo pDnsCacheInfo); + + IntPtr SniPacketAllocateWrapper(SafeHandle pConn, IoType ioType); + + uint SniPacketGetDataWrapper(IntPtr packet, byte[] readBuffer, uint readBufferLength, out uint dataSize); + + void SniPacketReset(SNIHandle pConn, IoType ioType, SNIPacket pPacket, ConsumerNumber consumer); + + void SniPacketRelease(IntPtr pPacket); + + unsafe void SniPacketSetData(SNIPacket pPacket, byte* pbBuffer, uint cbBuffer); + + uint SniQueryInfo(QueryType queryType, ref IntPtr pbQueryInfo); + + uint SniQueryInfo(QueryType queryType, ref uint pbQueryInfo); + + uint SniReadAsync(SNIHandle pConn, ref IntPtr ppNewPacket); + + uint SniReadSyncOverAsync(SNIHandle pConn, ref IntPtr ppNewPacket, int timeout); + + uint SniRemoveProvider(SNIHandle pConn, Provider provider); + + unsafe uint SniSecGenClientContextWrapper( + SNIHandle pConn, + byte* pIn, + uint cbIn, + byte* pOut, + ref uint pcbOut, + out bool pfDone, + byte* szServerInfo, + uint cbServerInfo, + string pwszUserName, + string pwszPassword); + + uint SniSecInitPackage(ref uint pcbMaxToken); + + void SniServerEnumClose(IntPtr packet); + + IntPtr SniServerEnumOpen(); + + int SniServerEnumRead(IntPtr packet, char[] readBuffer, int bufferLength, out bool more); + + uint SniSetInfo(SNIHandle pConn, QueryType queryType, ref uint pbQueryInfo); + + uint SniTerminate(); + + uint SniWaitForSslHandshakeToComplete(SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion); + + uint SniWriteAsyncWrapper(SNIHandle pConn, SNIPacket pPacket); + + uint SniWriteSyncOverAsync(SNIHandle pConn, SNIPacket pPacket); + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniError.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniError.cs index b349c2876d..2e519c15ab 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniError.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniError.cs @@ -12,7 +12,7 @@ internal struct SniError internal Provider provider; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 261)] internal string errorMessage; - internal uint nativeError; + internal int nativeError; internal uint sniError; [MarshalAs(UnmanagedType.LPWStr)] internal string fileName; diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethods.netcore.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethods.netcore.cs new file mode 100644 index 0000000000..a47af81e9f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethods.netcore.cs @@ -0,0 +1,319 @@ +// 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. + +#if NET + +using System; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Data.SqlClient; + +namespace Interop.Windows.Sni +{ + internal sealed class SniNativeMethods : ISniNativeMethods + { + private const string DllName = "Microsoft.Data.SqlClient.SNI.dll"; + + #region Interface Implementation + + public uint SniAddProvider(SNIHandle pConn, Provider provider, ref AuthProviderInfo pInfo) => + SNIAddProviderWrapper(pConn, provider, ref pInfo); + + public uint SniAddProvider(SNIHandle pConn, Provider provider, ref uint pInfo) => + SNIAddProviderWrapper(pConn, provider, ref pInfo); + + public uint SniCheckConnection(SNIHandle pConn) => + SNICheckConnectionWrapper(pConn); + + public uint SniClose(IntPtr pConn) => + SNICloseWrapper(pConn); + + public uint SniGetMaxComposedSpnLength() => + GetSniMaxComposedSpnLength(); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out bool pbQueryInfo) => + SNIGetInfoWrapper(pConn, queryType, out pbQueryInfo); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out Guid pbQueryInfo) => + SNIGetInfoWrapper(pConn, queryType, out pbQueryInfo); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out Provider provider) => + SNIGetInfoWrapper(pConn, queryType, out provider); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out ushort portNumber) => + SNIGetInfoWrapper(pConn, queryType, out portNumber); + + public void SniGetLastError(out SniError pLastError) => + SNIGetLastError(out pLastError); + + public uint SniGetPeerAddrStrWrapper( + SNIHandle pConn, + int bufferSize, + StringBuilder addrBuffer, + out uint addrLength) => + SNIGetPeerAddrStrWrapper(pConn, bufferSize, addrBuffer, out addrLength); + + public uint SniInitialize(IntPtr pmo) => + SNIInitialize(pmo); + + public uint SniIsTokenRestricted(IntPtr token, out bool isRestricted) => + UnmanagedIsTokenRestricted(token, out isRestricted); + + public uint SniOpenSyncExWrapper(ref SniClientConsumerInfo pClientConsumerInfo, out IntPtr ppConn) => + SNIOpenSyncExWrapper(ref pClientConsumerInfo, out ppConn); + + public uint SniOpenWrapper( + ref SniConsumerInfo pConsumerInfo, + string connect, + SNIHandle pConn, + out IntPtr ppConn, + bool fSync, + SqlConnectionIPAddressPreference ipPreference, + ref SniDnsCacheInfo pDnsCacheInfo) => + SNIOpenWrapper(ref pConsumerInfo, connect, pConn, out ppConn, fSync, ipPreference, ref pDnsCacheInfo); + + public IntPtr SniPacketAllocateWrapper(SafeHandle pConn, IoType ioType) => + SNIPacketAllocateWrapper(pConn, ioType); + + public uint SniPacketGetDataWrapper( + IntPtr packet, + byte[] readBuffer, + uint readBufferLength, + out uint dataSize) => + SNIPacketGetDataWrapper(packet, readBuffer, readBufferLength, out dataSize); + + public void SniPacketReset(SNIHandle pConn, IoType ioType, SNIPacket pPacket, ConsumerNumber consumer) => + SNIPacketResetWrapper(pConn, ioType, pPacket, consumer); + + public void SniPacketRelease(IntPtr pPacket) => + SNIPacketRelease(pPacket); + + public unsafe void SniPacketSetData(SNIPacket pPacket, byte* pbBuffer, uint cbBuffer) => + SNIPacketSetData(pPacket, pbBuffer, cbBuffer); + + public uint SniQueryInfo(QueryType queryType, ref IntPtr pbQueryInfo) => + SNIQueryInfo(queryType, ref pbQueryInfo); + + public uint SniQueryInfo(QueryType queryType, ref uint pbQueryInfo) => + SNIQueryInfo(queryType, ref pbQueryInfo); + + public uint SniReadAsync(SNIHandle pConn, ref IntPtr ppNewPacket) => + SNIReadAsyncWrapper(pConn, ref ppNewPacket); + + public uint SniReadSyncOverAsync(SNIHandle pConn, ref IntPtr ppNewPacket, int timeout) => + SNIReadSyncOverAsync(pConn, ref ppNewPacket, timeout); + + public uint SniRemoveProvider(SNIHandle pConn, Provider provider) => + SNIRemoveProviderWrapper(pConn, provider); + + public unsafe uint SniSecGenClientContextWrapper( + SNIHandle pConn, + byte* pIn, + uint cbIn, + byte* pOut, + ref uint pcbOut, + out bool pfDone, + byte* szServerInfo, + uint cbServerInfo, + string pwszUserName, + string pwszPassword) => + SNISecGenClientContextWrapper( + pConn, + pIn, + cbIn, + pOut, + ref pcbOut, + out pfDone, + szServerInfo, + cbServerInfo, + pwszUserName, + pwszPassword); + + public uint SniSecInitPackage(ref uint pcbMaxToken) => + SNISecInitPackage(ref pcbMaxToken); + + public void SniServerEnumClose(IntPtr packet) => + SNIServerEnumCloseWrapper(packet); + + public IntPtr SniServerEnumOpen() => + SNIServerEnumOpenWrapper(); + + public int SniServerEnumRead(IntPtr packet, char[] readBuffer, int bufferLength, out bool more) => + SNIServerEnumReadWrapper(packet, readBuffer, bufferLength, out more); + + public uint SniSetInfo(SNIHandle pConn, QueryType queryType, ref uint pbQueryInfo) => + SNISetInfoWrapper(pConn, queryType, ref pbQueryInfo); + + public uint SniTerminate() => + SNITerminate(); + + public uint SniWaitForSslHandshakeToComplete(SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion) => + SNIWaitForSSLHandshakeToCompleteWrapper(pConn, dwMilliseconds, out pProtocolVersion); + + public uint SniWriteAsyncWrapper(SNIHandle pConn, SNIPacket pPacket) => + SNIWriteAsyncWrapper(pConn, pPacket); + + public uint SniWriteSyncOverAsync(SNIHandle pConn, SNIPacket pPacket) => + SNIWriteSyncOverAsync(pConn, pPacket); + + #endregion + + #region DllImports + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint GetSniMaxComposedSpnLength(); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIAddProviderWrapper( + SNIHandle pConn, + Provider provider, + [In] ref AuthProviderInfo pInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIAddProviderWrapper(SNIHandle pConn, Provider provider, [In] ref uint pInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNICheckConnectionWrapper([In] SNIHandle pConn); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNICloseWrapper(IntPtr pConn); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper( + [In] SNIHandle pConn, + QueryType queryType, + [MarshalAs(UnmanagedType.Bool)] out bool pbQInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType queryType, out Guid pbQueryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType queryType, out Provider provider); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType queryType, out ushort portNum); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern void SNIGetLastError(out SniError pErrorStruct); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private static extern uint SNIGetPeerAddrStrWrapper( + [In] SNIHandle pConn, + int bufferSize, + StringBuilder addrBuffer, + out uint addrLength); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIInitialize([In] IntPtr pmo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIOpenSyncExWrapper( + ref SniClientConsumerInfo pClientConsumerInfo, + out IntPtr ppConn); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIOpenWrapper( + [In] ref SniConsumerInfo pConsumerInfo, + [MarshalAs(UnmanagedType.LPWStr)] string szConnect, + [In] SNIHandle pConn, + out IntPtr ppConn, + [MarshalAs(UnmanagedType.Bool)] bool fSync, + SqlConnectionIPAddressPreference ipPreference, + [In] ref SniDnsCacheInfo pDnsCacheInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr SNIPacketAllocateWrapper([In] SafeHandle pConn, IoType ioType); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIPacketGetDataWrapper( + [In] IntPtr packet, + [In, Out] byte[] readBuffer, + uint readBufferLength, + out uint dataSize); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern void SNIPacketRelease(IntPtr pPacket); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern void SNIPacketResetWrapper( + [In] SNIHandle pConn, + IoType ioType, + SNIPacket pPacket, + ConsumerNumber consumer); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern unsafe void SNIPacketSetData(SNIPacket pPacket, [In] byte* pbBuf, uint cbBuf); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIQueryInfo(QueryType queryType, ref IntPtr pbQueryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIQueryInfo(QueryType queryType, ref uint pbQueryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIReadAsyncWrapper(SNIHandle pConn, ref IntPtr ppNewPacket); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIReadSyncOverAsync(SNIHandle pConn, ref IntPtr ppNewPacket, int timeout); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIRemoveProviderWrapper(SNIHandle pConn, Provider provider); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern unsafe uint SNISecGenClientContextWrapper( + [In] SNIHandle pConn, + [In, Out] byte* pIn, + uint cbIn, + [In, Out] byte* pOut, + [In] ref uint pcbOut, + [MarshalAs(UnmanagedType.Bool)] out bool pfDone, + byte* szServerInfo, + uint cbServerInfo, + [MarshalAs(UnmanagedType.LPWStr)] string pwszUserName, + [MarshalAs(UnmanagedType.LPWStr)] string pwszPassword); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNISecInitPackage(ref uint pcbMaxToken); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern void SNIServerEnumCloseWrapper([In] IntPtr packet); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr SNIServerEnumOpenWrapper(); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private static extern int SNIServerEnumReadWrapper( + [In] IntPtr packet, + [In, Out][MarshalAs(UnmanagedType.LPArray)] char[] readBuffer, + [In] int bufferLength, + [MarshalAs(UnmanagedType.Bool)] out bool more); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNISetInfoWrapper(SNIHandle pConn, QueryType queryType, [In] ref uint pbQueryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNITerminate(); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIWaitForSSLHandshakeToCompleteWrapper( + [In] SNIHandle pConn, + int dwMilliseconds, + out uint pProtocolVersion); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIWriteAsyncWrapper(SNIHandle pConn, [In] SNIPacket pPacket); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIWriteSyncOverAsync(SNIHandle pConn, [In] SNIPacket pPacket); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint UnmanagedIsTokenRestricted( + [In] IntPtr token, + [MarshalAs(UnmanagedType.Bool)] out bool isRestricted); + + #endregion + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsArm64.netfx.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsArm64.netfx.cs new file mode 100644 index 0000000000..1c60f92443 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsArm64.netfx.cs @@ -0,0 +1,319 @@ +// 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. + +#if NETFRAMEWORK + +using System; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Data.SqlClient; + +namespace Interop.Windows.Sni +{ + internal sealed class SniNativeMethodsArm64 : ISniNativeMethods + { + private const string DllName = "Microsoft.Data.SqlClient.SNI.arm64.dll"; + + #region Interface Implementation + + public uint SniAddProvider(SNIHandle pConn, Provider provider, ref AuthProviderInfo pInfo) => + SNIAddProviderWrapper(pConn, provider, ref pInfo); + + public uint SniAddProvider(SNIHandle pConn, Provider provider, ref uint pInfo) => + SNIAddProviderWrapper(pConn, provider, ref pInfo); + + public uint SniCheckConnection(SNIHandle pConn) => + SNICheckConnectionWrapper(pConn); + + public uint SniClose(IntPtr pConn) => + SNICloseWrapper(pConn); + + public uint SniGetMaxComposedSpnLength() => + GetSniMaxComposedSpnLength(); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out bool pbQueryInfo) => + SNIGetInfoWrapper(pConn, queryType, out pbQueryInfo); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out Guid pbQueryInfo) => + SNIGetInfoWrapper(pConn, queryType, out pbQueryInfo); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out Provider provider) => + SNIGetInfoWrapper(pConn, queryType, out provider); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out ushort portNumber) => + SNIGetInfoWrapper(pConn, queryType, out portNumber); + + public void SniGetLastError(out SniError pLastError) => + SNIGetLastError(out pLastError); + + public uint SniGetPeerAddrStrWrapper( + SNIHandle pConn, + int bufferSize, + StringBuilder addrBuffer, + out uint addrLength) => + SNIGetPeerAddrStrWrapper(pConn, bufferSize, addrBuffer, out addrLength); + + public uint SniInitialize(IntPtr pmo) => + SNIInitialize(pmo); + + public uint SniIsTokenRestricted(IntPtr token, out bool isRestricted) => + UnmanagedIsTokenRestricted(token, out isRestricted); + + public uint SniOpenSyncExWrapper(ref SniClientConsumerInfo pClientConsumerInfo, out IntPtr ppConn) => + SNIOpenSyncExWrapper(ref pClientConsumerInfo, out ppConn); + + public uint SniOpenWrapper( + ref SniConsumerInfo pConsumerInfo, + string connect, + SNIHandle pConn, + out IntPtr ppConn, + bool fSync, + SqlConnectionIPAddressPreference ipPreference, + ref SniDnsCacheInfo pDnsCacheInfo) => + SNIOpenWrapper(ref pConsumerInfo, connect, pConn, out ppConn, fSync, ipPreference, ref pDnsCacheInfo); + + public IntPtr SniPacketAllocateWrapper(SafeHandle pConn, IoType ioType) => + SNIPacketAllocateWrapper(pConn, ioType); + + public uint SniPacketGetDataWrapper( + IntPtr packet, + byte[] readBuffer, + uint readBufferLength, + out uint dataSize) => + SNIPacketGetDataWrapper(packet, readBuffer, readBufferLength, out dataSize); + + public void SniPacketReset(SNIHandle pConn, IoType ioType, SNIPacket pPacket, ConsumerNumber consumer) => + SNIPacketResetWrapper(pConn, ioType, pPacket, consumer); + + public void SniPacketRelease(IntPtr pPacket) => + SNIPacketRelease(pPacket); + + public unsafe void SniPacketSetData(SNIPacket pPacket, byte* pbBuffer, uint cbBuffer) => + SNIPacketSetData(pPacket, pbBuffer, cbBuffer); + + public uint SniQueryInfo(QueryType queryType, ref IntPtr pbQueryInfo) => + SNIQueryInfo(queryType, ref pbQueryInfo); + + public uint SniQueryInfo(QueryType queryType, ref uint pbQueryInfo) => + SNIQueryInfo(queryType, ref pbQueryInfo); + + public uint SniReadAsync(SNIHandle pConn, ref IntPtr ppNewPacket) => + SNIReadAsyncWrapper(pConn, ref ppNewPacket); + + public uint SniReadSyncOverAsync(SNIHandle pConn, ref IntPtr ppNewPacket, int timeout) => + SNIReadSyncOverAsync(pConn, ref ppNewPacket, timeout); + + public uint SniRemoveProvider(SNIHandle pConn, Provider provider) => + SNIRemoveProviderWrapper(pConn, provider); + + public unsafe uint SniSecGenClientContextWrapper( + SNIHandle pConn, + byte* pIn, + uint cbIn, + byte* pOut, + ref uint pcbOut, + out bool pfDone, + byte* szServerInfo, + uint cbServerInfo, + string pwszUserName, + string pwszPassword) => + SNISecGenClientContextWrapper( + pConn, + pIn, + cbIn, + pOut, + ref pcbOut, + out pfDone, + szServerInfo, + cbServerInfo, + pwszUserName, + pwszPassword); + + public uint SniSecInitPackage(ref uint pcbMaxToken) => + SNISecInitPackage(ref pcbMaxToken); + + public void SniServerEnumClose(IntPtr packet) => + SNIServerEnumCloseWrapper(packet); + + public IntPtr SniServerEnumOpen() => + SNIServerEnumOpenWrapper(); + + public int SniServerEnumRead(IntPtr packet, char[] readBuffer, int bufferLength, out bool more) => + SNIServerEnumReadWrapper(packet, readBuffer, bufferLength, out more); + + public uint SniSetInfo(SNIHandle pConn, QueryType queryType, ref uint pbQueryInfo) => + SNISetInfoWrapper(pConn, queryType, ref pbQueryInfo); + + public uint SniTerminate() => + SNITerminate(); + + public uint SniWaitForSslHandshakeToComplete(SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion) => + SNIWaitForSSLHandshakeToCompleteWrapper(pConn, dwMilliseconds, out pProtocolVersion); + + public uint SniWriteAsyncWrapper(SNIHandle pConn, SNIPacket pPacket) => + SNIWriteAsyncWrapper(pConn, pPacket); + + public uint SniWriteSyncOverAsync(SNIHandle pConn, SNIPacket pPacket) => + SNIWriteSyncOverAsync(pConn, pPacket); + + #endregion + + #region DllImports + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint GetSniMaxComposedSpnLength(); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIAddProviderWrapper( + SNIHandle pConn, + Provider provider, + [In] ref AuthProviderInfo pInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIAddProviderWrapper(SNIHandle pConn, Provider provider, [In] ref uint pInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNICheckConnectionWrapper([In] SNIHandle pConn); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNICloseWrapper(IntPtr pConn); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper( + [In] SNIHandle pConn, + QueryType queryType, + [MarshalAs(UnmanagedType.Bool)] out bool pbQInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType queryType, out Guid pbQueryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType queryType, out Provider provider); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType queryType, out ushort portNum); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern void SNIGetLastError(out SniError pErrorStruct); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private static extern uint SNIGetPeerAddrStrWrapper( + [In] SNIHandle pConn, + int bufferSize, + StringBuilder addrBuffer, + out uint addrLength); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIInitialize([In] IntPtr pmo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIOpenSyncExWrapper( + ref SniClientConsumerInfo pClientConsumerInfo, + out IntPtr ppConn); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIOpenWrapper( + [In] ref SniConsumerInfo pConsumerInfo, + [MarshalAs(UnmanagedType.LPWStr)] string szConnect, + [In] SNIHandle pConn, + out IntPtr ppConn, + [MarshalAs(UnmanagedType.Bool)] bool fSync, + SqlConnectionIPAddressPreference ipPreference, + [In] ref SniDnsCacheInfo pDnsCacheInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr SNIPacketAllocateWrapper([In] SafeHandle pConn, IoType ioType); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIPacketGetDataWrapper( + [In] IntPtr packet, + [In, Out] byte[] readBuffer, + uint readBufferLength, + out uint dataSize); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern void SNIPacketRelease(IntPtr pPacket); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern void SNIPacketResetWrapper( + [In] SNIHandle pConn, + IoType ioType, + SNIPacket pPacket, + ConsumerNumber consumer); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern unsafe void SNIPacketSetData(SNIPacket pPacket, [In] byte* pbBuf, uint cbBuf); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIQueryInfo(QueryType queryType, ref IntPtr pbQueryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIQueryInfo(QueryType queryType, ref uint pbQueryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIReadAsyncWrapper(SNIHandle pConn, ref IntPtr ppNewPacket); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIReadSyncOverAsync(SNIHandle pConn, ref IntPtr ppNewPacket, int timeout); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIRemoveProviderWrapper(SNIHandle pConn, Provider provider); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern unsafe uint SNISecGenClientContextWrapper( + [In] SNIHandle pConn, + [In, Out] byte* pIn, + uint cbIn, + [In, Out] byte* pOut, + [In] ref uint pcbOut, + [MarshalAs(UnmanagedType.Bool)] out bool pfDone, + byte* szServerInfo, + uint cbServerInfo, + [MarshalAs(UnmanagedType.LPWStr)] string pwszUserName, + [MarshalAs(UnmanagedType.LPWStr)] string pwszPassword); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNISecInitPackage(ref uint pcbMaxToken); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern void SNIServerEnumCloseWrapper([In] IntPtr packet); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr SNIServerEnumOpenWrapper(); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private static extern int SNIServerEnumReadWrapper( + [In] IntPtr packet, + [In, Out][MarshalAs(UnmanagedType.LPArray)] char[] readBuffer, + [In] int bufferLength, + [MarshalAs(UnmanagedType.Bool)] out bool more); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNISetInfoWrapper(SNIHandle pConn, QueryType queryType, [In] ref uint pbQueryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNITerminate(); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIWaitForSSLHandshakeToCompleteWrapper( + [In] SNIHandle pConn, + int dwMilliseconds, + out uint pProtocolVersion); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIWriteAsyncWrapper(SNIHandle pConn, [In] SNIPacket pPacket); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIWriteSyncOverAsync(SNIHandle pConn, [In] SNIPacket pPacket); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint UnmanagedIsTokenRestricted( + [In] IntPtr token, + [MarshalAs(UnmanagedType.Bool)] out bool isRestricted); + + #endregion + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsNotSupported.netfx.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsNotSupported.netfx.cs new file mode 100644 index 0000000000..ba26fb0dc9 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsNotSupported.netfx.cs @@ -0,0 +1,156 @@ +// 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. + +#if NETFRAMEWORK + +using System; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Data.Common; +using Microsoft.Data.SqlClient; + +namespace Interop.Windows.Sni +{ + internal sealed class SniNativeMethodsNotSupported : ISniNativeMethods + { + private readonly string _architecture; + + public SniNativeMethodsNotSupported(Architecture architecture) + { + _architecture = architecture.ToString(); + } + + public uint SniAddProvider(SNIHandle pConn, Provider provider, ref AuthProviderInfo pInfo) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniAddProvider(SNIHandle pConn, Provider provider, ref uint pInfo) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniCheckConnection(SNIHandle pConn) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniClose(IntPtr pConn) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniGetMaxComposedSpnLength() => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out bool pbQueryInfo) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out Guid pbQueryInfo) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out Provider provider) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out ushort portNumber) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public void SniGetLastError(out SniError pLastError) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniGetPeerAddrStrWrapper( + SNIHandle pConn, + int bufferSize, + StringBuilder addrBuffer, + out uint addrLength) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniInitialize(IntPtr pmo) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniIsTokenRestricted(IntPtr token, out bool isRestricted) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniOpenSyncExWrapper(ref SniClientConsumerInfo pClientConsumerInfo, out IntPtr ppConn) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniOpenWrapper( + ref SniConsumerInfo pConsumerInfo, + string connect, + SNIHandle pConn, + out IntPtr ppConn, + bool fSync, + SqlConnectionIPAddressPreference ipPreference, + ref SniDnsCacheInfo pDnsCacheInfo) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public IntPtr SniPacketAllocateWrapper(SafeHandle pConn, IoType ioType) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniPacketGetDataWrapper( + IntPtr packet, + byte[] readBuffer, + uint readBufferLength, + out uint dataSize) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public void SniPacketReset(SNIHandle pConn, IoType ioType, SNIPacket pPacket, ConsumerNumber consumer) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public void SniPacketRelease(IntPtr pPacket) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public unsafe void SniPacketSetData(SNIPacket pPacket, byte* pbBuffer, uint cbBuffer) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniQueryInfo(QueryType queryType, ref IntPtr pbQueryInfo) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniQueryInfo(QueryType queryType, ref uint pbQueryInfo) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniReadAsync(SNIHandle pConn, ref IntPtr ppNewPacket) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniReadSyncOverAsync(SNIHandle pConn, ref IntPtr ppNewPacket, int timeout) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniRemoveProvider(SNIHandle pConn, Provider provider) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public unsafe uint SniSecGenClientContextWrapper( + SNIHandle pConn, + byte* pIn, + uint cbIn, + byte* pOut, + ref uint pcbOut, + out bool pfDone, + byte* szServerInfo, + uint cbServerInfo, + string pwszUserName, + string pwszPassword) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniSecInitPackage(ref uint pcbMaxToken) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public void SniServerEnumClose(IntPtr packet) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public IntPtr SniServerEnumOpen() => + throw ADP.SNIPlatformNotSupported(_architecture); + + public int SniServerEnumRead(IntPtr packet, char[] readBuffer, int bufferLength, out bool more) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniSetInfo(SNIHandle pConn, QueryType queryType, ref uint pbQueryInfo) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniTerminate() => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniWaitForSslHandshakeToComplete(SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniWriteAsyncWrapper(SNIHandle pConn, SNIPacket pPacket) => + throw ADP.SNIPlatformNotSupported(_architecture); + + public uint SniWriteSyncOverAsync(SNIHandle pConn, SNIPacket pPacket) => + throw ADP.SNIPlatformNotSupported(_architecture); + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsX64.netfx.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsX64.netfx.cs new file mode 100644 index 0000000000..1c6519327f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsX64.netfx.cs @@ -0,0 +1,319 @@ +// 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. + +#if NETFRAMEWORK + +using System; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Data.SqlClient; + +namespace Interop.Windows.Sni +{ + internal sealed class SniNativeMethodsX64 : ISniNativeMethods + { + private const string DllName = "Microsoft.Data.SqlClient.SNI.x64.dll"; + + #region Interface Implementation + + public uint SniAddProvider(SNIHandle pConn, Provider provider, ref AuthProviderInfo pInfo) => + SNIAddProviderWrapper(pConn, provider, ref pInfo); + + public uint SniAddProvider(SNIHandle pConn, Provider provider, ref uint pInfo) => + SNIAddProviderWrapper(pConn, provider, ref pInfo); + + public uint SniCheckConnection(SNIHandle pConn) => + SNICheckConnectionWrapper(pConn); + + public uint SniClose(IntPtr pConn) => + SNICloseWrapper(pConn); + + public uint SniGetMaxComposedSpnLength() => + GetSniMaxComposedSpnLength(); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out bool pbQueryInfo) => + SNIGetInfoWrapper(pConn, queryType, out pbQueryInfo); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out Guid pbQueryInfo) => + SNIGetInfoWrapper(pConn, queryType, out pbQueryInfo); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out Provider provider) => + SNIGetInfoWrapper(pConn, queryType, out provider); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out ushort portNumber) => + SNIGetInfoWrapper(pConn, queryType, out portNumber); + + public void SniGetLastError(out SniError pLastError) => + SNIGetLastError(out pLastError); + + public uint SniGetPeerAddrStrWrapper( + SNIHandle pConn, + int bufferSize, + StringBuilder addrBuffer, + out uint addrLength) => + SNIGetPeerAddrStrWrapper(pConn, bufferSize, addrBuffer, out addrLength); + + public uint SniInitialize(IntPtr pmo) => + SNIInitialize(pmo); + + public uint SniIsTokenRestricted(IntPtr token, out bool isRestricted) => + UnmanagedIsTokenRestricted(token, out isRestricted); + + public uint SniOpenSyncExWrapper(ref SniClientConsumerInfo pClientConsumerInfo, out IntPtr ppConn) => + SNIOpenSyncExWrapper(ref pClientConsumerInfo, out ppConn); + + public uint SniOpenWrapper( + ref SniConsumerInfo pConsumerInfo, + string connect, + SNIHandle pConn, + out IntPtr ppConn, + bool fSync, + SqlConnectionIPAddressPreference ipPreference, + ref SniDnsCacheInfo pDnsCacheInfo) => + SNIOpenWrapper(ref pConsumerInfo, connect, pConn, out ppConn, fSync, ipPreference, ref pDnsCacheInfo); + + public IntPtr SniPacketAllocateWrapper(SafeHandle pConn, IoType ioType) => + SNIPacketAllocateWrapper(pConn, ioType); + + public uint SniPacketGetDataWrapper( + IntPtr packet, + byte[] readBuffer, + uint readBufferLength, + out uint dataSize) => + SNIPacketGetDataWrapper(packet, readBuffer, readBufferLength, out dataSize); + + public void SniPacketReset(SNIHandle pConn, IoType ioType, SNIPacket pPacket, ConsumerNumber consumer) => + SNIPacketResetWrapper(pConn, ioType, pPacket, consumer); + + public void SniPacketRelease(IntPtr pPacket) => + SNIPacketRelease(pPacket); + + public unsafe void SniPacketSetData(SNIPacket pPacket, byte* pbBuffer, uint cbBuffer) => + SNIPacketSetData(pPacket, pbBuffer, cbBuffer); + + public uint SniQueryInfo(QueryType queryType, ref IntPtr pbQueryInfo) => + SNIQueryInfo(queryType, ref pbQueryInfo); + + public uint SniQueryInfo(QueryType queryType, ref uint pbQueryInfo) => + SNIQueryInfo(queryType, ref pbQueryInfo); + + public uint SniReadAsync(SNIHandle pConn, ref IntPtr ppNewPacket) => + SNIReadAsyncWrapper(pConn, ref ppNewPacket); + + public uint SniReadSyncOverAsync(SNIHandle pConn, ref IntPtr ppNewPacket, int timeout) => + SNIReadSyncOverAsync(pConn, ref ppNewPacket, timeout); + + public uint SniRemoveProvider(SNIHandle pConn, Provider provider) => + SNIRemoveProviderWrapper(pConn, provider); + + public unsafe uint SniSecGenClientContextWrapper( + SNIHandle pConn, + byte* pIn, + uint cbIn, + byte* pOut, + ref uint pcbOut, + out bool pfDone, + byte* szServerInfo, + uint cbServerInfo, + string pwszUserName, + string pwszPassword) => + SNISecGenClientContextWrapper( + pConn, + pIn, + cbIn, + pOut, + ref pcbOut, + out pfDone, + szServerInfo, + cbServerInfo, + pwszUserName, + pwszPassword); + + public uint SniSecInitPackage(ref uint pcbMaxToken) => + SNISecInitPackage(ref pcbMaxToken); + + public void SniServerEnumClose(IntPtr packet) => + SNIServerEnumCloseWrapper(packet); + + public IntPtr SniServerEnumOpen() => + SNIServerEnumOpenWrapper(); + + public int SniServerEnumRead(IntPtr packet, char[] readBuffer, int bufferLength, out bool more) => + SNIServerEnumReadWrapper(packet, readBuffer, bufferLength, out more); + + public uint SniSetInfo(SNIHandle pConn, QueryType queryType, ref uint pbQueryInfo) => + SNISetInfoWrapper(pConn, queryType, ref pbQueryInfo); + + public uint SniTerminate() => + SNITerminate(); + + public uint SniWaitForSslHandshakeToComplete(SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion) => + SNIWaitForSSLHandshakeToCompleteWrapper(pConn, dwMilliseconds, out pProtocolVersion); + + public uint SniWriteAsyncWrapper(SNIHandle pConn, SNIPacket pPacket) => + SNIWriteAsyncWrapper(pConn, pPacket); + + public uint SniWriteSyncOverAsync(SNIHandle pConn, SNIPacket pPacket) => + SNIWriteSyncOverAsync(pConn, pPacket); + + #endregion + + #region DllImports + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint GetSniMaxComposedSpnLength(); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIAddProviderWrapper( + SNIHandle pConn, + Provider provider, + [In] ref AuthProviderInfo pInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIAddProviderWrapper(SNIHandle pConn, Provider provider, [In] ref uint pInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNICheckConnectionWrapper([In] SNIHandle pConn); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNICloseWrapper(IntPtr pConn); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper( + [In] SNIHandle pConn, + QueryType queryType, + [MarshalAs(UnmanagedType.Bool)] out bool pbQInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType queryType, out Guid pbQueryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType queryType, out Provider provider); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType queryType, out ushort portNum); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern void SNIGetLastError(out SniError pErrorStruct); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private static extern uint SNIGetPeerAddrStrWrapper( + [In] SNIHandle pConn, + int bufferSize, + StringBuilder addrBuffer, + out uint addrLength); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIInitialize([In] IntPtr pmo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIOpenSyncExWrapper( + ref SniClientConsumerInfo pClientConsumerInfo, + out IntPtr ppConn); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIOpenWrapper( + [In] ref SniConsumerInfo pConsumerInfo, + [MarshalAs(UnmanagedType.LPWStr)] string szConnect, + [In] SNIHandle pConn, + out IntPtr ppConn, + [MarshalAs(UnmanagedType.Bool)] bool fSync, + SqlConnectionIPAddressPreference ipPreference, + [In] ref SniDnsCacheInfo pDnsCacheInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr SNIPacketAllocateWrapper([In] SafeHandle pConn, IoType ioType); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIPacketGetDataWrapper( + [In] IntPtr packet, + [In, Out] byte[] readBuffer, + uint readBufferLength, + out uint dataSize); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern void SNIPacketRelease(IntPtr pPacket); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern void SNIPacketResetWrapper( + [In] SNIHandle pConn, + IoType ioType, + SNIPacket pPacket, + ConsumerNumber consumer); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern unsafe void SNIPacketSetData(SNIPacket pPacket, [In] byte* pbBuf, uint cbBuf); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIQueryInfo(QueryType queryType, ref IntPtr pbQueryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIQueryInfo(QueryType queryType, ref uint pbQueryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIReadAsyncWrapper(SNIHandle pConn, ref IntPtr ppNewPacket); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIReadSyncOverAsync(SNIHandle pConn, ref IntPtr ppNewPacket, int timeout); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIRemoveProviderWrapper(SNIHandle pConn, Provider provider); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern unsafe uint SNISecGenClientContextWrapper( + [In] SNIHandle pConn, + [In, Out] byte* pIn, + uint cbIn, + [In, Out] byte* pOut, + [In] ref uint pcbOut, + [MarshalAs(UnmanagedType.Bool)] out bool pfDone, + byte* szServerInfo, + uint cbServerInfo, + [MarshalAs(UnmanagedType.LPWStr)] string pwszUserName, + [MarshalAs(UnmanagedType.LPWStr)] string pwszPassword); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNISecInitPackage(ref uint pcbMaxToken); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern void SNIServerEnumCloseWrapper([In] IntPtr packet); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr SNIServerEnumOpenWrapper(); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private static extern int SNIServerEnumReadWrapper( + [In] IntPtr packet, + [In, Out][MarshalAs(UnmanagedType.LPArray)] char[] readBuffer, + [In] int bufferLength, + [MarshalAs(UnmanagedType.Bool)] out bool more); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNISetInfoWrapper(SNIHandle pConn, QueryType queryType, [In] ref uint pbQueryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNITerminate(); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIWaitForSSLHandshakeToCompleteWrapper( + [In] SNIHandle pConn, + int dwMilliseconds, + out uint pProtocolVersion); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIWriteAsyncWrapper(SNIHandle pConn, [In] SNIPacket pPacket); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIWriteSyncOverAsync(SNIHandle pConn, [In] SNIPacket pPacket); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint UnmanagedIsTokenRestricted( + [In] IntPtr token, + [MarshalAs(UnmanagedType.Bool)] out bool isRestricted); + + #endregion + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsX86.netfx.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsX86.netfx.cs new file mode 100644 index 0000000000..fd2ab4644b --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeMethodsX86.netfx.cs @@ -0,0 +1,319 @@ +// 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. + +#if NETFRAMEWORK + +using System; +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.Data.SqlClient; + +namespace Interop.Windows.Sni +{ + internal sealed class SniNativeMethodsX86 : ISniNativeMethods + { + private const string DllName = "Microsoft.Data.SqlClient.SNI.x86.dll"; + + #region Interface Implementation + + public uint SniAddProvider(SNIHandle pConn, Provider provider, ref AuthProviderInfo pInfo) => + SNIAddProviderWrapper(pConn, provider, ref pInfo); + + public uint SniAddProvider(SNIHandle pConn, Provider provider, ref uint pInfo) => + SNIAddProviderWrapper(pConn, provider, ref pInfo); + + public uint SniCheckConnection(SNIHandle pConn) => + SNICheckConnectionWrapper(pConn); + + public uint SniClose(IntPtr pConn) => + SNICloseWrapper(pConn); + + public uint SniGetMaxComposedSpnLength() => + GetSniMaxComposedSpnLength(); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out bool pbQueryInfo) => + SNIGetInfoWrapper(pConn, queryType, out pbQueryInfo); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out Guid pbQueryInfo) => + SNIGetInfoWrapper(pConn, queryType, out pbQueryInfo); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out Provider provider) => + SNIGetInfoWrapper(pConn, queryType, out provider); + + public uint SniGetInfoWrapper(SNIHandle pConn, QueryType queryType, out ushort portNumber) => + SNIGetInfoWrapper(pConn, queryType, out portNumber); + + public void SniGetLastError(out SniError pLastError) => + SNIGetLastError(out pLastError); + + public uint SniGetPeerAddrStrWrapper( + SNIHandle pConn, + int bufferSize, + StringBuilder addrBuffer, + out uint addrLength) => + SNIGetPeerAddrStrWrapper(pConn, bufferSize, addrBuffer, out addrLength); + + public uint SniInitialize(IntPtr pmo) => + SNIInitialize(pmo); + + public uint SniIsTokenRestricted(IntPtr token, out bool isRestricted) => + UnmanagedIsTokenRestricted(token, out isRestricted); + + public uint SniOpenSyncExWrapper(ref SniClientConsumerInfo pClientConsumerInfo, out IntPtr ppConn) => + SNIOpenSyncExWrapper(ref pClientConsumerInfo, out ppConn); + + public uint SniOpenWrapper( + ref SniConsumerInfo pConsumerInfo, + string connect, + SNIHandle pConn, + out IntPtr ppConn, + bool fSync, + SqlConnectionIPAddressPreference ipPreference, + ref SniDnsCacheInfo pDnsCacheInfo) => + SNIOpenWrapper(ref pConsumerInfo, connect, pConn, out ppConn, fSync, ipPreference, ref pDnsCacheInfo); + + public IntPtr SniPacketAllocateWrapper(SafeHandle pConn, IoType ioType) => + SNIPacketAllocateWrapper(pConn, ioType); + + public uint SniPacketGetDataWrapper( + IntPtr packet, + byte[] readBuffer, + uint readBufferLength, + out uint dataSize) => + SNIPacketGetDataWrapper(packet, readBuffer, readBufferLength, out dataSize); + + public void SniPacketReset(SNIHandle pConn, IoType ioType, SNIPacket pPacket, ConsumerNumber consumer) => + SNIPacketResetWrapper(pConn, ioType, pPacket, consumer); + + public void SniPacketRelease(IntPtr pPacket) => + SNIPacketRelease(pPacket); + + public unsafe void SniPacketSetData(SNIPacket pPacket, byte* pbBuffer, uint cbBuffer) => + SNIPacketSetData(pPacket, pbBuffer, cbBuffer); + + public uint SniQueryInfo(QueryType queryType, ref IntPtr pbQueryInfo) => + SNIQueryInfo(queryType, ref pbQueryInfo); + + public uint SniQueryInfo(QueryType queryType, ref uint pbQueryInfo) => + SNIQueryInfo(queryType, ref pbQueryInfo); + + public uint SniReadAsync(SNIHandle pConn, ref IntPtr ppNewPacket) => + SNIReadAsyncWrapper(pConn, ref ppNewPacket); + + public uint SniReadSyncOverAsync(SNIHandle pConn, ref IntPtr ppNewPacket, int timeout) => + SNIReadSyncOverAsync(pConn, ref ppNewPacket, timeout); + + public uint SniRemoveProvider(SNIHandle pConn, Provider provider) => + SNIRemoveProviderWrapper(pConn, provider); + + public unsafe uint SniSecGenClientContextWrapper( + SNIHandle pConn, + byte* pIn, + uint cbIn, + byte* pOut, + ref uint pcbOut, + out bool pfDone, + byte* szServerInfo, + uint cbServerInfo, + string pwszUserName, + string pwszPassword) => + SNISecGenClientContextWrapper( + pConn, + pIn, + cbIn, + pOut, + ref pcbOut, + out pfDone, + szServerInfo, + cbServerInfo, + pwszUserName, + pwszPassword); + + public uint SniSecInitPackage(ref uint pcbMaxToken) => + SNISecInitPackage(ref pcbMaxToken); + + public void SniServerEnumClose(IntPtr packet) => + SNIServerEnumCloseWrapper(packet); + + public IntPtr SniServerEnumOpen() => + SNIServerEnumOpenWrapper(); + + public int SniServerEnumRead(IntPtr packet, char[] readBuffer, int bufferLength, out bool more) => + SNIServerEnumReadWrapper(packet, readBuffer, bufferLength, out more); + + public uint SniSetInfo(SNIHandle pConn, QueryType queryType, ref uint pbQueryInfo) => + SNISetInfoWrapper(pConn, queryType, ref pbQueryInfo); + + public uint SniTerminate() => + SNITerminate(); + + public uint SniWaitForSslHandshakeToComplete(SNIHandle pConn, int dwMilliseconds, out uint pProtocolVersion) => + SNIWaitForSSLHandshakeToCompleteWrapper(pConn, dwMilliseconds, out pProtocolVersion); + + public uint SniWriteAsyncWrapper(SNIHandle pConn, SNIPacket pPacket) => + SNIWriteAsyncWrapper(pConn, pPacket); + + public uint SniWriteSyncOverAsync(SNIHandle pConn, SNIPacket pPacket) => + SNIWriteSyncOverAsync(pConn, pPacket); + + #endregion + + #region DllImports + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint GetSniMaxComposedSpnLength(); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIAddProviderWrapper( + SNIHandle pConn, + Provider provider, + [In] ref AuthProviderInfo pInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIAddProviderWrapper(SNIHandle pConn, Provider provider, [In] ref uint pInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNICheckConnectionWrapper([In] SNIHandle pConn); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNICloseWrapper(IntPtr pConn); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper( + [In] SNIHandle pConn, + QueryType queryType, + [MarshalAs(UnmanagedType.Bool)] out bool pbQInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType queryType, out Guid pbQueryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType queryType, out Provider provider); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIGetInfoWrapper([In] SNIHandle pConn, QueryType queryType, out ushort portNum); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern void SNIGetLastError(out SniError pErrorStruct); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private static extern uint SNIGetPeerAddrStrWrapper( + [In] SNIHandle pConn, + int bufferSize, + StringBuilder addrBuffer, + out uint addrLength); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIInitialize([In] IntPtr pmo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIOpenSyncExWrapper( + ref SniClientConsumerInfo pClientConsumerInfo, + out IntPtr ppConn); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIOpenWrapper( + [In] ref SniConsumerInfo pConsumerInfo, + [MarshalAs(UnmanagedType.LPWStr)] string szConnect, + [In] SNIHandle pConn, + out IntPtr ppConn, + [MarshalAs(UnmanagedType.Bool)] bool fSync, + SqlConnectionIPAddressPreference ipPreference, + [In] ref SniDnsCacheInfo pDnsCacheInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr SNIPacketAllocateWrapper([In] SafeHandle pConn, IoType ioType); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIPacketGetDataWrapper( + [In] IntPtr packet, + [In, Out] byte[] readBuffer, + uint readBufferLength, + out uint dataSize); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern void SNIPacketRelease(IntPtr pPacket); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern void SNIPacketResetWrapper( + [In] SNIHandle pConn, + IoType ioType, + SNIPacket pPacket, + ConsumerNumber consumer); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern unsafe void SNIPacketSetData(SNIPacket pPacket, [In] byte* pbBuf, uint cbBuf); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIQueryInfo(QueryType queryType, ref IntPtr pbQueryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIQueryInfo(QueryType queryType, ref uint pbQueryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIReadAsyncWrapper(SNIHandle pConn, ref IntPtr ppNewPacket); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIReadSyncOverAsync(SNIHandle pConn, ref IntPtr ppNewPacket, int timeout); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIRemoveProviderWrapper(SNIHandle pConn, Provider provider); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern unsafe uint SNISecGenClientContextWrapper( + [In] SNIHandle pConn, + [In, Out] byte* pIn, + uint cbIn, + [In, Out] byte* pOut, + [In] ref uint pcbOut, + [MarshalAs(UnmanagedType.Bool)] out bool pfDone, + byte* szServerInfo, + uint cbServerInfo, + [MarshalAs(UnmanagedType.LPWStr)] string pwszUserName, + [MarshalAs(UnmanagedType.LPWStr)] string pwszPassword); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNISecInitPackage(ref uint pcbMaxToken); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern void SNIServerEnumCloseWrapper([In] IntPtr packet); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr SNIServerEnumOpenWrapper(); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private static extern int SNIServerEnumReadWrapper( + [In] IntPtr packet, + [In, Out][MarshalAs(UnmanagedType.LPArray)] char[] readBuffer, + [In] int bufferLength, + [MarshalAs(UnmanagedType.Bool)] out bool more); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNISetInfoWrapper(SNIHandle pConn, QueryType queryType, [In] ref uint pbQueryInfo); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNITerminate(); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIWaitForSSLHandshakeToCompleteWrapper( + [In] SNIHandle pConn, + int dwMilliseconds, + out uint pProtocolVersion); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIWriteAsyncWrapper(SNIHandle pConn, [In] SNIPacket pPacket); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint SNIWriteSyncOverAsync(SNIHandle pConn, [In] SNIPacket pPacket); + + [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] + private static extern uint UnmanagedIsTokenRestricted( + [In] IntPtr token, + [MarshalAs(UnmanagedType.Bool)] out bool isRestricted); + + #endregion + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeWrapper.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeWrapper.cs new file mode 100644 index 0000000000..c9cbef64a4 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SniNativeWrapper.cs @@ -0,0 +1,525 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Buffers; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; +using Interop.Windows.Sni; +using Microsoft.Data.Common; +using Microsoft.Data.SqlClient.Utilities; + +#if NETFRAMEWORK +using System.Runtime.CompilerServices; +using System.Runtime.Versioning; +using System.Security; +using Interop.Windows; +#endif + +namespace Microsoft.Data.SqlClient +{ + internal static class SniNativeWrapper + { + #region Member Variables + + private const int SniIpv6AddrStringBufferLength = 48; + + #if NET + private const int SniOpenTimeOut = -1; + #endif + + #if NETFRAMEWORK + private static readonly ISniNativeMethods s_nativeMethods = RuntimeInformation.ProcessArchitecture switch + { + Architecture.Arm64 => new SniNativeMethodsArm64(), + Architecture.X64 => new SniNativeMethodsX64(), + Architecture.X86 => new SniNativeMethodsX86(), + _ => new SniNativeMethodsNotSupported(RuntimeInformation.ProcessArchitecture) + }; + #else + private static readonly SniNativeMethods s_nativeMethods = new SniNativeMethods(); + #endif + + private static int s_sniMaxComposedSpnLength = -1; + + #endregion + + internal static int SniMaxComposedSpnLength + { + get + { + if (s_sniMaxComposedSpnLength == -1) + { + s_sniMaxComposedSpnLength = checked((int)s_nativeMethods.SniGetMaxComposedSpnLength()); + } + return s_sniMaxComposedSpnLength; + } + } + + #region Public Methods + + internal static uint SniAddProvider(SNIHandle pConn, Provider provNum, ref AuthProviderInfo pInfo) => + s_nativeMethods.SniAddProvider(pConn, provNum, ref pInfo); + + #if NETFRAMEWORK + [ResourceExposure(ResourceScope.None)] + [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] + internal static uint SniAddProvider(SNIHandle pConn, + Provider providerEnum, + AuthProviderInfo authInfo) + { + Debug.Assert(authInfo.clientCertificateCallback == null, "CTAIP support has been removed"); + + uint ret = SniAddProvider(pConn, providerEnum, ref authInfo); + if (ret == SystemErrors.ERROR_SUCCESS) + { + // added a provider, need to requery for sync over async support + ret = s_nativeMethods.SniGetInfoWrapper(pConn, QueryType.SNI_QUERY_CONN_SUPPORTS_SYNC_OVER_ASYNC, out bool _); + Debug.Assert(ret == SystemErrors.ERROR_SUCCESS, "SNIGetInfo cannot fail with this QType"); + } + + return ret; + } + #endif + + internal static uint SniAddProvider(SNIHandle pConn, Provider provNum, ref uint pInfo) => + s_nativeMethods.SniAddProvider(pConn, provNum, ref pInfo); + + internal static uint SniCheckConnection(SNIHandle pConn) => + s_nativeMethods.SniCheckConnection(pConn); + + internal static uint SniClose(IntPtr pConn) => + s_nativeMethods.SniClose(pConn); + + internal static uint SniGetConnectionId(SNIHandle pConn, ref Guid connId) => + s_nativeMethods.SniGetInfoWrapper(pConn, QueryType.SNI_QUERY_CONN_CONNID, out connId); + + internal static uint SniGetConnectionIpString(SNIHandle pConn, ref string connIpStr) + { + StringBuilder addrBuffer = new StringBuilder(SniIpv6AddrStringBufferLength); + + uint ret = s_nativeMethods.SniGetPeerAddrStrWrapper( + pConn, + SniIpv6AddrStringBufferLength, + addrBuffer, + out uint connIpLen); + + connIpStr = addrBuffer.ToString(0, Convert.ToInt32(connIpLen)); + + return ret; + } + + internal static uint SniGetConnectionPort(SNIHandle pConn, ref ushort portNum) => + s_nativeMethods.SniGetInfoWrapper(pConn, QueryType.SNI_QUERY_CONN_PEERPORT, out portNum); + + internal static void SniGetLastError(out SniError pErrorStruct) => + s_nativeMethods.SniGetLastError(out pErrorStruct); + + internal static uint SniGetProviderNumber(SNIHandle pConn, ref Provider provNum) => + s_nativeMethods.SniGetInfoWrapper(pConn, QueryType.SNI_QUERY_CONN_PROVIDERNUM, out provNum); + + internal static uint SniInitialize() => + s_nativeMethods.SniInitialize(IntPtr.Zero); + + internal static uint SniIsTokenRestricted(IntPtr token, out bool isRestricted) + { + uint result = s_nativeMethods.SniIsTokenRestricted(token, out isRestricted); + if (result != 0) + { + Marshal.ThrowExceptionForHR(unchecked((int)result)); + } + + return result; + } + + internal static uint SniOpenMarsSession( + ConsumerInfo consumerInfo, + SNIHandle parent, + ref IntPtr pConn, + bool fSync, + SqlConnectionIPAddressPreference ipPreference, + SQLDNSInfo cachedDnsInfo) + { + // initialize consumer info for MARS + SniConsumerInfo nativeConsumerInfo = new SniConsumerInfo(); + MarshalConsumerInfo(consumerInfo, ref nativeConsumerInfo); + + SniDnsCacheInfo nativeCachedDnsInfo = new SniDnsCacheInfo() + { + wszCachedFQDN = cachedDnsInfo?.FQDN, + wszCachedTcpIPv4 = cachedDnsInfo?.AddrIPv4, + wszCachedTcpIPv6 = cachedDnsInfo?.AddrIPv6, + wszCachedTcpPort = cachedDnsInfo?.Port, + }; + + return s_nativeMethods.SniOpenWrapper( + pConsumerInfo: ref nativeConsumerInfo, + connect: "session:", + pConn: parent, + ppConn: out pConn, + fSync, + ipPreference, + pDnsCacheInfo: ref nativeCachedDnsInfo); + } + + internal static unsafe uint SniOpenSyncEx( + ConsumerInfo consumerInfo, + string connString, + ref IntPtr pConn, + ref string spn, + byte[] instanceName, + bool fOverrideCache, + bool fSync, + int timeout, + bool fParallel, + + #if NETFRAMEWORK + int transparentNetworkResolutionStateNo, + int totalTimeout, + #endif + + SqlConnectionIPAddressPreference ipPreference, + SQLDNSInfo cachedDnsInfo, + string hostNameInCertificate) + { + fixed (byte* pInstanceName = instanceName) + { + SniClientConsumerInfo clientConsumerInfo = new SniClientConsumerInfo(); + + // initialize client ConsumerInfo part first + MarshalConsumerInfo(consumerInfo, ref clientConsumerInfo.ConsumerInfo); + + clientConsumerInfo.wszConnectionString = connString; + clientConsumerInfo.HostNameInCertificate = hostNameInCertificate; + clientConsumerInfo.networkLibrary = Prefix.UNKNOWN_PREFIX; + clientConsumerInfo.szInstanceName = pInstanceName; + clientConsumerInfo.cchInstanceName = (uint)instanceName.Length; + clientConsumerInfo.fOverrideLastConnectCache = fOverrideCache; + clientConsumerInfo.fSynchronousConnection = fSync; + clientConsumerInfo.timeout = timeout; + clientConsumerInfo.fParallel = fParallel; + + #if NETFRAMEWORK + switch (transparentNetworkResolutionStateNo) + { + case 0: + clientConsumerInfo.transparentNetworkResolution = TransparentNetworkResolutionMode.DisabledMode; + break; + case 1: + clientConsumerInfo.transparentNetworkResolution = TransparentNetworkResolutionMode.SequentialMode; + break; + case 2: + clientConsumerInfo.transparentNetworkResolution = TransparentNetworkResolutionMode.ParallelMode; + break; + }; + clientConsumerInfo.totalTimeout = totalTimeout; + #else + clientConsumerInfo.transparentNetworkResolution = TransparentNetworkResolutionMode.DisabledMode; + clientConsumerInfo.totalTimeout = SniOpenTimeOut; + #endif + + clientConsumerInfo.isAzureSqlServerEndpoint = ADP.IsAzureSqlServerEndpoint(connString); + + clientConsumerInfo.ipAddressPreference = ipPreference; + clientConsumerInfo.DNSCacheInfo.wszCachedFQDN = cachedDnsInfo?.FQDN; + clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv4 = cachedDnsInfo?.AddrIPv4; + clientConsumerInfo.DNSCacheInfo.wszCachedTcpIPv6 = cachedDnsInfo?.AddrIPv6; + clientConsumerInfo.DNSCacheInfo.wszCachedTcpPort = cachedDnsInfo?.Port; + + if (spn is not null) + { + if (spn.Length == 0) + { + // An empty string implies we need to find the SPN so we supply a buffer for the max size + var array = ArrayPool.Shared.Rent(SniMaxComposedSpnLength); + array.AsSpan().Clear(); + + try + { + fixed (byte* pin_spnBuffer = array) + { + clientConsumerInfo.szSPN = pin_spnBuffer; + clientConsumerInfo.cchSPN = (uint)SniMaxComposedSpnLength; + + var result = s_nativeMethods.SniOpenSyncExWrapper(ref clientConsumerInfo, out pConn); + if (result == 0) + { + spn = Encoding.Unicode.GetString(array).TrimEnd('\0'); + } + + return result; + } + } + finally + { + ArrayPool.Shared.Return(array); + } + } + else + { + // We have a value of the SPN, so we marshal that and send it to the native layer + var writer = ObjectPools.BufferWriter.Rent(); + + try + { + // Native SNI requires the Unicode encoding and any other encoding like UTF8 breaks the code. + Encoding.Unicode.GetBytes(spn, writer); + Trace.Assert(writer.WrittenCount <= SniMaxComposedSpnLength, + "Length of the provided SPN exceeded the buffer size."); + + fixed (byte* pin_spnBuffer = writer.WrittenSpan) + { + clientConsumerInfo.szSPN = pin_spnBuffer; + clientConsumerInfo.cchSPN = (uint)writer.WrittenCount; + return s_nativeMethods.SniOpenSyncExWrapper(ref clientConsumerInfo, out pConn); + } + } + finally + { + ObjectPools.BufferWriter.Return(writer); + } + } + } + + // Otherwise leave szSPN null (SQL Auth) + return s_nativeMethods.SniOpenSyncExWrapper(ref clientConsumerInfo, out pConn); + } + } + + internal static void SniPacketAllocate(SafeHandle pConn, IoType ioType, ref IntPtr pPacket) => + pPacket = s_nativeMethods.SniPacketAllocateWrapper(pConn, ioType); + + internal static uint SniPacketGetData(IntPtr packet, byte[] readBuffer, ref uint dataSize) => + s_nativeMethods.SniPacketGetDataWrapper(packet, readBuffer, (uint)readBuffer.Length, out dataSize); + + internal static void SniPacketRelease(IntPtr pPacket) => + s_nativeMethods.SniPacketRelease(pPacket); + + internal static unsafe void SniPacketSetData(SNIPacket packet, byte[] data, int length) + { + fixed (byte* pData = data) + { + s_nativeMethods.SniPacketSetData(packet, pData, (uint)length); + } + } + + #if NETFRAMEWORK + // Notes on SecureString: Writing out security sensitive information to managed buffer + // should be avoided as these can be moved around by GC. There are two set of + // information which falls into this category: passwords and new changed password which + // are passed in as SecureString by a user. Writing out clear passwords information is + // delayed until this layer to ensure that the information is written out to buffer + // which is pinned in this method already. This also ensures that processing a clear + // password is done right before it is written out to SNI_Packet where gets encrypted + // properly. TdsParserStaticMethods.EncryptPassword operation is also done here to + // minimize the time the clear password is held in memory. Any time loose encryption + // algorithms are changed it should be done in both in this method and + // TdsParserStaticMethods.EncryptPassword. + // Up to current release, it is also guaranteed that both password and new change + // password will fit into a single login packet whose size is fixed to 4096 So, no + // splitting logic is needed. + internal static void SniPacketSetData( + SNIPacket packet, + byte[] data, + int length, + SecureString[] passwords, // pointer to the passwords which need to be written out to SNI Packet + int[] passwordOffsets) // Offset into data buffer where the password to be written out to + { + Debug.Assert(passwords is null || (passwordOffsets is not null && passwords.Length == passwordOffsets.Length), "The number of passwords does not match the number of password offsets"); + + bool mustRelease = false; + bool mustClearBuffer = false; + IntPtr clearPassword = IntPtr.Zero; + + // provides a guaranteed finally block – without this it isn’t guaranteed – non- + // interruptible by fatal exceptions + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + unsafe + { + if (passwords != null) + { + // Process SecureString + for (int i = 0; i < passwords.Length; ++i) + { + // SecureString is used + if (passwords[i] != null) + { + // provides a guaranteed finally block – without this it isn’t + // guaranteed – non-interruptible by fatal exceptions + RuntimeHelpers.PrepareConstrainedRegions(); + try + { + // ============================================================ + // Get the clear text of secure string without converting it + // to string type + // ============================================================ + clearPassword = Marshal.SecureStringToCoTaskMemUnicode(passwords[i]); + + // ============================================================ + // Loosely encrypt the clear text - The encryption algorithm + // should exactly match the TdsParserStaticMethods.EncryptPassword + // ============================================================ + char* pwChar = (char*)clearPassword.ToPointer(); + byte* pByte = (byte*)clearPassword.ToPointer(); + + int passwordsLength = passwords[i].Length; + for (int j = 0; j < passwordsLength; ++j) + { + int s = *pwChar; + byte bLo = (byte)(s & 0xff); + byte bHi = (byte)((s >> 8) & 0xff); + *(pByte++) = (byte)((((bLo & 0x0f) << 4) | (bLo >> 4)) ^ 0xa5); + *(pByte++) = (byte)((((bHi & 0x0f) << 4) | (bHi >> 4)) ^ 0xa5); + ++pwChar; + } + + // ============================================================ + // Write out the loosely encrypted passwords to data buffer + // ============================================================ + mustClearBuffer = true; + Marshal.Copy(clearPassword, data, passwordOffsets[i], passwordsLength * 2); + } + finally + { + // Make sure that we clear the security sensitive information + if (clearPassword != IntPtr.Zero) + { + Marshal.ZeroFreeCoTaskMemUnicode(clearPassword); + } + } + } + } + } + + packet.DangerousAddRef(ref mustRelease); + Debug.Assert(mustRelease, "AddRef Failed!"); + + SniPacketSetData(packet, data, length); + } + } + finally + { + if (mustRelease) + { + packet.DangerousRelease(); + } + + // Make sure that we clear the security sensitive information + // data->Initialize() is not safe to call under CER + if (mustClearBuffer) + { + for (int i = 0; i < data.Length; ++i) + { + data[i] = 0; + } + } + } + } + #endif + + internal static void SniPacketReset(SNIHandle pConn, IoType ioType, SNIPacket pPacket, ConsumerNumber consNum) => + s_nativeMethods.SniPacketReset(pConn, ioType, pPacket, consNum); + + internal static uint SniQueryInfo(QueryType qType, ref uint pbQInfo) => + s_nativeMethods.SniQueryInfo(qType, ref pbQInfo); + + internal static uint SniQueryInfo(QueryType qType, ref IntPtr pbQInfo) => + s_nativeMethods.SniQueryInfo(qType, ref pbQInfo); + + internal static uint SniReadAsync(SNIHandle pConn, ref IntPtr ppNewPacket) => + s_nativeMethods.SniReadAsync(pConn, ref ppNewPacket); + + internal static uint SniReadSyncOverAsync(SNIHandle pConn, ref IntPtr ppNewPacket, int timeout) => + s_nativeMethods.SniReadSyncOverAsync(pConn, ref ppNewPacket, timeout); + + internal static uint SniRemoveProvider(SNIHandle pConn, Provider provNum) => + s_nativeMethods.SniRemoveProvider(pConn, provNum); + + internal static unsafe uint SniSecGenClientContext( + SNIHandle pConnectionObject, + ReadOnlySpan inBuff, + Span outBuff, + ref uint sendLength, + string serverUserName) + { + var serverWriter = ObjectPools.BufferWriter.Rent(); + + try + { + Encoding.Unicode.GetBytes(serverUserName, serverWriter); + + fixed (byte* pInBuff = inBuff) + fixed (byte* pOutBuff = outBuff) + fixed (byte* pServerInfo = serverWriter.WrittenSpan) + { + return s_nativeMethods.SniSecGenClientContextWrapper( + pConn: pConnectionObject, + pIn: pInBuff, + cbIn: (uint)inBuff.Length, + pOut: pOutBuff, + pcbOut: ref sendLength, + pfDone: out _, + szServerInfo: pServerInfo, + cbServerInfo: (uint)serverWriter.WrittenSpan.Length, + pwszUserName: null, + pwszPassword: null); + } + } + finally + { + ObjectPools.BufferWriter.Return(serverWriter); + } + } + + internal static uint SniSecInitPackage(ref uint pcbMaxToken) => + s_nativeMethods.SniSecInitPackage(ref pcbMaxToken); + + internal static void SniServerEnumClose(IntPtr packet) => + s_nativeMethods.SniServerEnumClose(packet); + + internal static IntPtr SniServerEnumOpen() => + s_nativeMethods.SniServerEnumOpen(); + + internal static int SniServerEnumRead(IntPtr packet, char[] readBuffer, int bufferLength, out bool more) => + s_nativeMethods.SniServerEnumRead(packet, readBuffer, bufferLength, out more); + + internal static uint SniSetInfo(SNIHandle pConn, QueryType qType, ref uint pbQInfo) => + s_nativeMethods.SniSetInfo(pConn, qType, ref pbQInfo); + + internal static uint SniTerminate() => + s_nativeMethods.SniTerminate(); + + internal static uint SniWaitForSslHandshakeToComplete( + SNIHandle pConn, + int dwMilliseconds, + out uint pProtocolVersion) => + s_nativeMethods.SniWaitForSslHandshakeToComplete(pConn, dwMilliseconds, out pProtocolVersion); + + internal static uint SniWritePacket(SNIHandle pConn, SNIPacket packet, bool sync) => + sync + ? s_nativeMethods.SniWriteSyncOverAsync(pConn, packet) + : s_nativeMethods.SniWriteAsyncWrapper(pConn, packet); + + #endregion + + #region Private Methods + + private static void MarshalConsumerInfo(ConsumerInfo consumerInfo, ref SniConsumerInfo nativeConsumerInfo) + { + nativeConsumerInfo.DefaultUserDataLength = consumerInfo.defaultBufferSize; + nativeConsumerInfo.fnReadComp = consumerInfo.readDelegate is not null + ? Marshal.GetFunctionPointerForDelegate(consumerInfo.readDelegate) + : IntPtr.Zero; + nativeConsumerInfo.fnWriteComp = consumerInfo.writeDelegate is not null + ? Marshal.GetFunctionPointerForDelegate(consumerInfo.writeDelegate) + : IntPtr.Zero; + nativeConsumerInfo.ConsumerKey = consumerInfo.key; + } + + #endregion + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SqlDependencyProcessDispatcherStorage.netfx.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SqlDependencyProcessDispatcherStorage.netfx.cs index c4693a32b4..dddfcb5e07 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SqlDependencyProcessDispatcherStorage.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/Sni/SqlDependencyProcessDispatcherStorage.netfx.cs @@ -4,52 +4,50 @@ #if NETFRAMEWORK +using System; using System.Diagnostics; using System.Runtime.InteropServices; -using System.Threading; +using System.Runtime.Versioning; namespace Interop.Windows.Sni { - internal unsafe class SqlDependencyProcessDispatcherStorage + internal class SqlDependencyProcessDispatcherStorage { - static void* data; + private static readonly object s_lockObj = new(); + private static IntPtr s_data; + private static int s_size; - static int size; - static volatile int thelock; // Int used for a spin-lock. - - public static void* NativeGetData(out int passedSize) + [ResourceExposure(ResourceScope.Process)] // SxS: there is no way to set scope = Instance, using Process which is wider + [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] + public static byte[] NativeGetData() { - passedSize = size; - return data; + byte[] result = null; + if (s_data != IntPtr.Zero) + { + result = new byte[s_size]; + Marshal.Copy(s_data, result, 0, s_size); + } + + return result; } - internal static bool NativeSetData(void* passedData, int passedSize) + [ResourceExposure(ResourceScope.Process)] // SxS: there is no way to set scope = Instance, using Process which is wider + [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] + internal static void NativeSetData(byte[] data) { - bool success = false; - - while (0 != Interlocked.CompareExchange(ref thelock, 1, 0)) - { // Spin until we have the lock. - Thread.Sleep(50); // Sleep with short-timeout to prevent starvation. - } - Trace.Assert(1 == thelock); // Now that we have the lock, lock should be equal to 1. - - if (data == null) + lock (s_lockObj) { - data = Marshal.AllocHGlobal(passedSize).ToPointer(); - - Trace.Assert(data != null); - - System.Buffer.MemoryCopy(passedData, data, passedSize, passedSize); - - Trace.Assert(0 == size); // Size should still be zero at this point. - size = passedSize; - success = true; + if (s_data == IntPtr.Zero) + { + s_data = Marshal.AllocHGlobal(data.Length); + Trace.Assert(s_data != IntPtr.Zero); + + Marshal.Copy(data, 0, s_data, data.Length); + + Trace.Assert(s_size == 0); // Size should still be zero at this point + s_size = data.Length; + } } - - int result = Interlocked.CompareExchange(ref thelock, 0, 1); - Trace.Assert(1 == result); // The release of the lock should have been successful. - - return success; } } } diff --git a/src/Microsoft.Data.SqlClient/src/Interop/Windows/SystemErrors.cs b/src/Microsoft.Data.SqlClient/src/Interop/Windows/SystemErrors.cs index 6a2edef310..40819ef4d9 100644 --- a/src/Microsoft.Data.SqlClient/src/Interop/Windows/SystemErrors.cs +++ b/src/Microsoft.Data.SqlClient/src/Interop/Windows/SystemErrors.cs @@ -5,8 +5,10 @@ namespace Interop.Windows { // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382.aspx - internal partial class SystemErrors + internal class SystemErrors { + internal const int ERROR_SUCCESS = 0x00; + internal const int ERROR_FILE_NOT_FOUND = 0x2; internal const int ERROR_INVALID_HANDLE = 0x6; internal const int ERROR_SHARING_VIOLATION = 0x20; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj index 6f5595b591..3a1d19a96d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/src/Microsoft.Data.SqlClient.csproj @@ -4,14 +4,26 @@ true - - - - - - - - + + + + + + + + + + + + + + + - + + + + + + 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 97e6a2039a..470cb8e35e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/AdapterUtil.cs @@ -18,6 +18,7 @@ using System.Threading; using System.Threading.Tasks; using System.Transactions; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.SqlClient; using IsolationLevel = System.Data.IsolationLevel; using Microsoft.Identity.Client; @@ -47,6 +48,7 @@ internal static partial class ADP { // NOTE: Initializing a Task in SQL CLR requires the "UNSAFE" permission set (http://msdn.microsoft.com/en-us/library/ms172338.aspx) // Therefore we are lazily initializing these Tasks to avoid forcing customers to use the "UNSAFE" set when they are actually using no Async features + // @TODO: These are not necessary because the TPL has optimized commonly used task return values like true and false. private static Task s_trueTask; internal static Task TrueTask => s_trueTask ??= Task.FromResult(true); @@ -750,24 +752,63 @@ internal static Version GetAssemblyVersion() private const string ONDEMAND_PREFIX = "-ondemand"; private const string AZURE_SYNAPSE = "-ondemand.sql.azuresynapse."; + private const string FABRIC_DATAWAREHOUSE = "datawarehouse.fabric.microsoft.com"; + private const string PBI_DATAWAREHOUSE = "datawarehouse.pbidedicated.microsoft.com"; + private const string PBI_DATAWAREHOUSE2 = ".pbidedicated.microsoft.com"; + private const string PBI_DATAWAREHOUSE3 = ".pbidedicated.windows.net"; + private const string AZURE_SQL = ".database.windows.net"; + private const string AZURE_SQL_GERMANY = ".database.cloudapi.de"; + private const string AZURE_SQL_USGOV = ".database.usgovcloudapi.net"; + private const string AZURE_SQL_CHINA = ".database.chinacloudapi.cn"; + private const string AZURE_SQL_FABRIC = ".database.fabric.microsoft.com"; + + /// + /// Represents a collection of Azure SQL Server endpoint URLs for various regions and environments. + /// + /// This array includes endpoint URLs for Azure SQL in global, Germany, US Government, + /// China, and Fabric environments. These endpoints are used to identify and interact with Azure SQL services + /// in their respective regions or environments. + internal static readonly string[] s_azureSqlServerEndpoints = { AZURE_SQL, + AZURE_SQL_GERMANY, + AZURE_SQL_USGOV, + AZURE_SQL_CHINA, + AZURE_SQL_FABRIC }; + + /// + /// Contains endpoint strings for Azure SQL Server on-demand services. + /// Each entry is a combination of the ONDEMAND_PREFIX and a specific Azure SQL endpoint string. + /// Example format: "ondemand.database.windows.net". + /// + internal static readonly string[] s_azureSqlServerOnDemandEndpoints = { ONDEMAND_PREFIX + AZURE_SQL, + ONDEMAND_PREFIX + AZURE_SQL_GERMANY, + ONDEMAND_PREFIX + AZURE_SQL_USGOV, + ONDEMAND_PREFIX + AZURE_SQL_CHINA, + ONDEMAND_PREFIX + AZURE_SQL_FABRIC }; + /// + /// Represents a collection of endpoint identifiers for Azure Synapse and related services. + /// + /// This array contains predefined endpoint strings used to identify Azure Synapse and + /// associated services, such as Fabric Data Warehouse and Power BI Data Warehouse. + internal static readonly string[] s_azureSynapseEndpoints = { FABRIC_DATAWAREHOUSE, + PBI_DATAWAREHOUSE, + PBI_DATAWAREHOUSE2, + PBI_DATAWAREHOUSE3 }; + + internal static readonly string[] s_azureSynapseOnDemandEndpoints = [.. s_azureSqlServerOnDemandEndpoints, .. s_azureSynapseEndpoints]; internal static bool IsAzureSynapseOnDemandEndpoint(string dataSource) { - return IsEndpoint(dataSource, ONDEMAND_PREFIX) || dataSource.Contains(AZURE_SYNAPSE); + return IsEndpoint(dataSource, s_azureSynapseOnDemandEndpoints) + || dataSource.IndexOf(AZURE_SYNAPSE, StringComparison.OrdinalIgnoreCase) >= 0; } - - internal static readonly string[] s_azureSqlServerEndpoints = { StringsHelper.GetString(Strings.AZURESQL_GenericEndpoint), - StringsHelper.GetString(Strings.AZURESQL_GermanEndpoint), - StringsHelper.GetString(Strings.AZURESQL_UsGovEndpoint), - StringsHelper.GetString(Strings.AZURESQL_ChinaEndpoint)}; - + internal static bool IsAzureSqlServerEndpoint(string dataSource) { - return IsEndpoint(dataSource, null); + return IsEndpoint(dataSource, s_azureSqlServerEndpoints); } // This method assumes dataSource parameter is in TCP connection string format. - private static bool IsEndpoint(string dataSource, string prefix) + private static bool IsEndpoint(string dataSource, string[] endpoints) { int length = dataSource.Length; // remove server port @@ -777,8 +818,17 @@ private static bool IsEndpoint(string dataSource, string prefix) length = foundIndex; } - // check for the instance name - foundIndex = dataSource.LastIndexOf('\\', length - 1, length - 1); + // Safeguard LastIndexOf call to avoid ArgumentOutOfRangeException when length is 0 + if (length > 0) + { + // check for the instance name + foundIndex = dataSource.LastIndexOf('\\', length - 1, length - 1); + } + else + { + foundIndex = -1; + } + if (foundIndex > 0) { length = foundIndex; @@ -791,10 +841,9 @@ private static bool IsEndpoint(string dataSource, string prefix) } // check if servername ends with any endpoints - for (int index = 0; index < s_azureSqlServerEndpoints.Length; index++) + foreach (var endpoint in endpoints) { - string endpoint = string.IsNullOrEmpty(prefix) ? s_azureSqlServerEndpoints[index] : prefix + s_azureSqlServerEndpoints[index]; - if (length > endpoint.Length) + if (length >= endpoint.Length) { if (string.Compare(dataSource, length - endpoint.Length, endpoint, 0, endpoint.Length, StringComparison.OrdinalIgnoreCase) == 0) { @@ -945,7 +994,7 @@ internal enum InternalErrorCode SqlDependencyObtainProcessDispatcherFailureObjectHandle = 50, SqlDependencyProcessDispatcherFailureCreateInstance = 51, - SqlDependencyProcessDispatcherFailureAppDomain = 52, + SqlDependencyCommandHashIsNotAssociatedWithNotification = 53, UnknownTransactionFailure = 60, @@ -1092,6 +1141,15 @@ internal static ArgumentException BadParameterName(string parameterName) return e; } + internal static Exception NullOutputParameterValueForVector(string paramName) + => InvalidOperation(StringsHelper.GetString(Strings.ADP_NullOutputParameterValueForVector, paramName)); + + internal static ArgumentException InvalidVectorHeader() + => Argument(StringsHelper.GetString(Strings.ADP_InvalidVectorHeader)); + + internal static Exception InvalidJsonStringForVector(string value, Exception inner) + => InvalidOperation(StringsHelper.GetString(Strings.ADP_InvalidJsonStringForVector, value), inner); + internal static Exception DeriveParametersNotSupported(IDbCommand value) => DataAdapter(StringsHelper.GetString(Strings.ADP_DeriveParametersNotSupported, value.GetType().Name, value.CommandType.ToString())); @@ -1312,7 +1370,6 @@ internal static Exception InvalidMixedUsageOfAccessTokenCallbackAndIntegratedSec => InvalidOperation(StringsHelper.GetString(Strings.ADP_InvalidMixedUsageOfAccessTokenCallbackAndIntegratedSecurity)); #endregion - internal static bool IsEmpty(string str) => string.IsNullOrEmpty(str); internal static readonly IntPtr s_ptrZero = IntPtr.Zero; #if NETFRAMEWORK #region netfx project only @@ -1323,13 +1380,6 @@ internal static Task CreatedTaskWithException(Exception ex) return completion.Task; } - internal static Task CreatedTaskWithCancellation() - { - TaskCompletionSource completion = new(); - completion.SetCanceled(); - return completion.Task; - } - // // Helper Functions // diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/AttestationProtocolUtilities.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/AttestationProtocolUtilities.cs new file mode 100644 index 0000000000..75e6eede3f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/AttestationProtocolUtilities.cs @@ -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. + +using System; +using System.Diagnostics; +using Microsoft.Data.SqlClient; + +namespace Microsoft.Data.Common.ConnectionString +{ + internal static class AttestationProtocolUtilities + { + /// + /// Convert a string value to the corresponding SqlConnectionAttestationProtocol + /// + /// + /// + /// + internal static bool TryConvertToAttestationProtocol(string value, out SqlConnectionAttestationProtocol result) + { + if (StringComparer.InvariantCultureIgnoreCase.Equals(value, nameof(SqlConnectionAttestationProtocol.HGS))) + { + result = SqlConnectionAttestationProtocol.HGS; + return true; + } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, nameof(SqlConnectionAttestationProtocol.AAS))) + { + result = SqlConnectionAttestationProtocol.AAS; + return true; + } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, nameof(SqlConnectionAttestationProtocol.None))) + { + result = SqlConnectionAttestationProtocol.None; + return true; + } + else + { + result = DbConnectionStringDefaults.AttestationProtocol; + return false; + } + } + + internal static bool IsValidAttestationProtocol(SqlConnectionAttestationProtocol value) + { + Debug.Assert(Enum.GetNames(typeof(SqlConnectionAttestationProtocol)).Length == 4, "SqlConnectionAttestationProtocol enum has changed, update needed"); + return value == SqlConnectionAttestationProtocol.NotSpecified + || value == SqlConnectionAttestationProtocol.HGS + || value == SqlConnectionAttestationProtocol.AAS + || value == SqlConnectionAttestationProtocol.None; + } + + internal static string AttestationProtocolToString(SqlConnectionAttestationProtocol value) + { + Debug.Assert(IsValidAttestationProtocol(value), "value is not a valid attestation protocol"); + + return value switch + { + SqlConnectionAttestationProtocol.AAS => nameof(SqlConnectionAttestationProtocol.AAS), + SqlConnectionAttestationProtocol.HGS => nameof(SqlConnectionAttestationProtocol.HGS), + SqlConnectionAttestationProtocol.None => nameof(SqlConnectionAttestationProtocol.None), + _ => null + }; + } + + internal static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(string keyword, object value) + { + if (value == null) + { + return DbConnectionStringDefaults.AttestationProtocol; + } + + if (value is string sValue) + { + // try again after remove leading & trailing whitespaces. + sValue = sValue.Trim(); + if (TryConvertToAttestationProtocol(sValue, out SqlConnectionAttestationProtocol result)) + { + return result; + } + + // string values must be valid + throw ADP.InvalidConnectionOptionValue(keyword); + } + else + { + // the value is not string, try other options + SqlConnectionAttestationProtocol eValue; + + if (value is SqlConnectionAttestationProtocol protocol) + { + eValue = protocol; + } + else if (value.GetType().IsEnum) + { + // explicitly block scenarios in which user tries to use wrong enum types, like: + // builder["SqlConnectionAttestationProtocol"] = EnvironmentVariableTarget.Process; + // workaround: explicitly cast non-SqlConnectionAttestationProtocol enums to int + throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionAttestationProtocol), null); + } + else + { + try + { + // Enum.ToObject allows only integral and enum values (enums are blocked above), raising ArgumentException for the rest + eValue = (SqlConnectionAttestationProtocol)Enum.ToObject(typeof(SqlConnectionAttestationProtocol), value); + } + catch (ArgumentException e) + { + // to be consistent with the messages we send in case of wrong type usage, replace + // the error with our exception, and keep the original one as inner one for troubleshooting + throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionAttestationProtocol), e); + } + } + + if (IsValidAttestationProtocol(eValue)) + { + return eValue; + } + else + { + throw ADP.InvalidEnumerationValue(typeof(SqlConnectionAttestationProtocol), (int)eValue); + } + } + } + + internal static SqlConnectionEncryptOption ConvertToSqlConnectionEncryptOption(string keyword, object value) + { + if (value is null) + { + return DbConnectionStringDefaults.Encrypt; + } + else if(value is SqlConnectionEncryptOption eValue) + { + return eValue; + } + else if (value is string sValue) + { + return SqlConnectionEncryptOption.Parse(sValue); + } + else if(value is bool bValue) + { + return SqlConnectionEncryptOption.Parse(bValue); + } + + throw ADP.InvalidConnectionOptionValue(keyword); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.Debug.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.Debug.cs new file mode 100644 index 0000000000..867deb1ac3 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.Debug.cs @@ -0,0 +1,208 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Text.RegularExpressions; +using Microsoft.Data.SqlClient; + +namespace Microsoft.Data.Common.ConnectionString +{ + internal partial class DbConnectionOptions + { + #if DEBUG + private const string ConnectionStringPattern = // may not contain embedded null except trailing last value + "([\\s;]*" // leading whitespace and extra semicolons + + "(?![\\s;])" // key does not start with space or semicolon + + "(?([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}]|\\s+==|==)+)" // allow any visible character for keyname except '=' which must quoted as '==' + + "\\s*=(?!=)\\s*" // the equal sign divides the key and value parts + + "(?" + + "(\"([^\"\u0000]|\"\")*\")" // double-quoted string, " must be quoted as "" + + "|" + + "('([^'\u0000]|'')*')" // single-quoted string, ' must be quoted as '' + + "|" + + "((?![\"'\\s])" // unquoted value must not start with " or ' or space, would also like = but too late to change + + "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*" // control characters must be quoted + + "(?([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}])+)" // allow any visible character for keyname except '=' + + "\\s*=\\s*" // the equal sign divides the key and value parts + + "(?" + + "(\\{([^\\}\u0000]|\\}\\})*\\})" // quoted string, starts with { and ends with } + + "|" + + "((?![\\{\\s])" // unquoted value must not start with { or space, would also like = but too late to change + + "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*" // control characters must be quoted + + ")" // although the spec does not allow {} embedded within a value, the retail code does. + + ")(\\s*)(;|[\u0000\\s]*$)" // whitespace after value up to semicolon or end-of-line + + ")*" // repeat the key-value pair + + "[\\s;]*[\u0000\\s]*"; // trailing whitespace/semicolons (DataSourceLocator), embedded nulls are allowed only in the end + private static readonly Regex ConnectionStringRegexOdbc = new Regex(ConnectionStringPatternOdbc, RegexOptions.ExplicitCapture | RegexOptions.Compiled); + #endif + + [Conditional("DEBUG")] + private static void DebugTraceKeyValuePair(string keyname, string keyvalue, Dictionary synonyms) + { + if (SqlClientEventSource.Log.IsAdvancedTraceOn()) + { + Debug.Assert(string.Equals(keyname, keyname?.ToLower(), StringComparison.InvariantCulture), "missing ToLower"); + string realkeyname = synonyms != null ? synonyms[keyname] : keyname; + + if (!CompareInsensitiveInvariant(DbConnectionStringKeywords.Password, realkeyname) && + !CompareInsensitiveInvariant(DbConnectionStringSynonyms.Pwd, realkeyname)) + { + // don't trace passwords ever! + if (keyvalue != null) + { + SqlClientEventSource.Log.AdvancedTraceEvent(" KeyName='{0}', KeyValue='{1}'", keyname, keyvalue); + } + else + { + SqlClientEventSource.Log.AdvancedTraceEvent(" KeyName='{0}'", keyname); + } + } + } + } + + #if DEBUG + private static void ParseComparison( + Dictionary parseTable, + string connectionString, + Dictionary synonyms, + bool firstKey, + Exception e) + { + try + { + var parsedValues = SplitConnectionString(connectionString, synonyms, firstKey); + foreach (var parsedValue in parsedValues) + { + string key = parsedValue.Key; + string value1 = parsedValue.Value; + + bool parseTableContainsKey = parseTable.TryGetValue(key, out string value2); + Debug.Assert(parseTableContainsKey, $"{nameof(ParseInternal)} code vs. regex mismatch keyname <{key}>"); + Debug.Assert(value1 == value2, $"{nameof(ParseInternal)} code vs. regex mismatch keyvalue <{value1}> <{value2}>"); + } + } + catch (ArgumentException f) + { + if (e != null) + { + string msg1 = e.Message; + string msg2 = f.Message; + + const string KeywordNotSupportedMessagePrefix = "Keyword not supported:"; + const string WrongFormatMessagePrefix = "Format of the initialization string"; + bool isEquivalent = msg1 == msg2; + if (!isEquivalent) + { + // We also accept cases were Regex parser (debug only) reports "wrong format" and + // retail parsing code reports format exception in different location or "keyword not supported" + if (msg2.StartsWith(WrongFormatMessagePrefix, StringComparison.Ordinal)) + { + if (msg1.StartsWith(KeywordNotSupportedMessagePrefix, StringComparison.Ordinal) || + msg1.StartsWith(WrongFormatMessagePrefix, StringComparison.Ordinal)) + { + isEquivalent = true; + } + } + } + + Debug.Assert(isEquivalent, "ParseInternal code vs regex message mismatch: <" + msg1 + "> <" + msg2 + ">"); + } + else + { + Debug.Fail("ParseInternal code vs regex throw mismatch " + f.Message); + } + + e = null; + } + + if (e != null) + { + Debug.Fail("ParseInternal code threw exception vs regex mismatch"); + } + } + #endif + + #if DEBUG + private static Dictionary SplitConnectionString( + string connectionString, + Dictionary synonyms, + bool firstKey) + { + var parseTable = new Dictionary(); + Regex parser = firstKey ? ConnectionStringRegexOdbc : ConnectionStringRegex; + + const int KeyIndex = 1, ValueIndex = 2; + Debug.Assert(KeyIndex == parser.GroupNumberFromName("key"), "wrong key index"); + Debug.Assert(ValueIndex == parser.GroupNumberFromName("value"), "wrong value index"); + + if (connectionString != null) + { + Match match = parser.Match(connectionString); + if (!match.Success || match.Length != connectionString.Length) + { + throw ADP.ConnectionStringSyntax(match.Length); + } + + int indexValue = 0; + CaptureCollection keyValues = match.Groups[ValueIndex].Captures; + foreach (Capture keypair in match.Groups[KeyIndex].Captures) + { + string keyName = (firstKey ? keypair.Value : keypair.Value.Replace("==", "=")).ToLower(CultureInfo.InvariantCulture); + string keyValue = keyValues[indexValue++].Value; + if (0 < keyValue.Length) + { + if (!firstKey) + { + switch (keyValue[0]) + { + case '\"': + keyValue = keyValue.Substring(1, keyValue.Length - 2).Replace("\"\"", "\""); + break; + case '\'': + keyValue = keyValue.Substring(1, keyValue.Length - 2).Replace("\'\'", "\'"); + break; + default: + break; + } + } + } + else + { + keyValue = null; + } + + DebugTraceKeyValuePair(keyName, keyValue, synonyms); + string realKeyName = synonyms != null + ? synonyms.TryGetValue(keyName, out string synonym) ? synonym : null + : keyName; + + if (!IsKeyNameValid(realKeyName)) + { + throw ADP.KeywordNotSupported(keyName); + } + + if (!firstKey || !parseTable.ContainsKey(realKeyName)) + { + parseTable[realKeyName] = keyValue; // last key-value pair wins (or first) + } + } + } + + return parseTable; + } + #endif + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionOptions.Common.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.cs similarity index 65% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionOptions.Common.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.cs index f72eb5ee59..39d66a41e0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionOptions.Common.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionOptions.cs @@ -6,88 +6,23 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.IO; using System.Text; using System.Text.RegularExpressions; -using Microsoft.Data.SqlClient; -namespace Microsoft.Data.Common +#if NETFRAMEWORK +using System.Runtime.Versioning; +using System.Security; +#endif + +namespace Microsoft.Data.Common.ConnectionString { - partial class DbConnectionOptions + internal partial class DbConnectionOptions { // instances of this class are intended to be immutable, i.e readonly // used by pooling classes so it is much easier to verify correctness // when not worried about the class being modified during execution - // connection string common keywords - private static class KEY - { - internal const string Integrated_Security = DbConnectionStringKeywords.IntegratedSecurity; - internal const string Password = DbConnectionStringKeywords.Password; - internal const string Persist_Security_Info = DbConnectionStringKeywords.PersistSecurityInfo; - internal const string User_ID = DbConnectionStringKeywords.UserID; - internal const string Encrypt = DbConnectionStringKeywords.Encrypt; - } - - // known connection string common synonyms - private static class SYNONYM - { - internal const string Pwd = DbConnectionStringSynonyms.Pwd; - internal const string UID = DbConnectionStringSynonyms.UID; - } - -#if DEBUG - /*private const string ConnectionStringPatternV1 = - "[\\s;]*" - +"(?([^=\\s]|\\s+[^=\\s]|\\s+==|==)+)" - + "\\s*=(?!=)\\s*" - +"(?(" - + "(" + "\"" + "([^\"]|\"\")*" + "\"" + ")" - + "|" - + "(" + "'" + "([^']|'')*" + "'" + ")" - + "|" - + "(" + "(?![\"'])" + "([^\\s;]|\\s+[^\\s;])*" + "(?([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}]|\\s+==|==)+)" // allow any visible character for keyname except '=' which must quoted as '==' - + "\\s*=(?!=)\\s*" // the equal sign divides the key and value parts - + "(?" - + "(\"([^\"\u0000]|\"\")*\")" // double quoted string, " must be quoted as "" - + "|" - + "('([^'\u0000]|'')*')" // single quoted string, ' must be quoted as '' - + "|" - + "((?![\"'\\s])" // unquoted value must not start with " or ' or space, would also like = but too late to change - + "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*" // control characters must be quoted - + "(?([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}])+)" // allow any visible character for keyname except '=' - + "\\s*=\\s*" // the equal sign divides the key and value parts - + "(?" - + "(\\{([^\\}\u0000]|\\}\\})*\\})" // quoted string, starts with { and ends with } - + "|" - + "((?![\\{\\s])" // unquoted value must not start with { or space, would also like = but too late to change - + "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*" // control characters must be quoted - - + ")" // although the spec does not allow {} - // embedded within a value, the retail code does. - + ")(\\s*)(;|[\u0000\\s]*$)" // whitespace after value up to semicolon or end-of-line - + ")*" // repeat the key-value pair - + "[\\s;]*[\u0000\\s]*" // trailing whitespace/semicolons (DataSourceLocator), embedded nulls are allowed only in the end - ; - - private static readonly Regex s_connectionStringRegex = new Regex(ConnectionStringPattern, RegexOptions.ExplicitCapture | RegexOptions.Compiled); - private static readonly Regex s_connectionStringRegexOdbc = new Regex(ConnectionStringPatternOdbc, RegexOptions.ExplicitCapture | RegexOptions.Compiled); -#endif private const string ConnectionStringValidKeyPattern = "^(?![;\\s])[^\\p{Cc}]+(? synonyms) { _parsetable = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - _usersConnectionString = connectionString != null ? connectionString : ""; + _usersConnectionString = connectionString ?? ""; // first pass on parsing, initial syntax check - if (0 < _usersConnectionString.Length) + if (_usersConnectionString.Length > 0) { _keyChain = ParseInternal(_parsetable, _usersConnectionString, true, synonyms, false); - _hasPasswordKeyword = (_parsetable.ContainsKey(KEY.Password) || _parsetable.ContainsKey(SYNONYM.Pwd)); - _hasUserIdKeyword = (_parsetable.ContainsKey(KEY.User_ID) || _parsetable.ContainsKey(SYNONYM.UID)); + _hasPasswordKeyword = _parsetable.ContainsKey(DbConnectionStringKeywords.Password) || + _parsetable.ContainsKey(DbConnectionStringSynonyms.Pwd); + _hasUserIdKeyword = _parsetable.ContainsKey(DbConnectionStringKeywords.UserID) || + _parsetable.ContainsKey(DbConnectionStringSynonyms.UID); } } @@ -138,29 +75,35 @@ protected DbConnectionOptions(DbConnectionOptions connectionOptions) // same as Boolean, but with SSPI thrown in as valid yes public bool ConvertValueToIntegratedSecurity() { - return _parsetable.TryGetValue(KEY.Integrated_Security, out string value) && value != null ? - ConvertValueToIntegratedSecurityInternal(value) : - false; + return _parsetable.TryGetValue(DbConnectionStringKeywords.IntegratedSecurity, out string value) && value != null + ? ConvertValueToIntegratedSecurityInternal(value) + : false; } internal bool ConvertValueToIntegratedSecurityInternal(string stringValue) { if (CompareInsensitiveInvariant(stringValue, "sspi") || CompareInsensitiveInvariant(stringValue, "true") || CompareInsensitiveInvariant(stringValue, "yes")) + { return true; - else if (CompareInsensitiveInvariant(stringValue, "false") || CompareInsensitiveInvariant(stringValue, "no")) + } + + if (CompareInsensitiveInvariant(stringValue, "false") || CompareInsensitiveInvariant(stringValue, "no")) + { return false; - else + } + + string tmp = stringValue.Trim(); // Remove leading & trailing whitespace. + if (CompareInsensitiveInvariant(tmp, "sspi") || CompareInsensitiveInvariant(tmp, "true") || CompareInsensitiveInvariant(tmp, "yes")) { - string tmp = stringValue.Trim(); // Remove leading & trailing whitespace. - if (CompareInsensitiveInvariant(tmp, "sspi") || CompareInsensitiveInvariant(tmp, "true") || CompareInsensitiveInvariant(tmp, "yes")) - return true; - else if (CompareInsensitiveInvariant(tmp, "false") || CompareInsensitiveInvariant(tmp, "no")) - return false; - else - { - throw ADP.InvalidConnectionOptionValue(KEY.Integrated_Security); - } + return true; } + + if (CompareInsensitiveInvariant(tmp, "false") || CompareInsensitiveInvariant(tmp, "no")) + { + return false; + } + + throw ADP.InvalidConnectionOptionValue(DbConnectionStringKeywords.IntegratedSecurity); } public int ConvertValueToInt32(string keyName, int defaultValue) @@ -207,9 +150,9 @@ private string UsersConnectionString(bool hidePassword, bool forceHidePassword) return connectionString ?? string.Empty; } - internal bool HasPersistablePassword => _hasPasswordKeyword ? - ConvertValueToBoolean(KEY.Persist_Security_Info, DbConnectionStringDefaults.PersistSecurityInfo) : - true; // no password means persistable password so we don't have to munge + internal bool HasPersistablePassword => _hasPasswordKeyword + ? ConvertValueToBoolean(DbConnectionStringKeywords.PersistSecurityInfo, DbConnectionStringDefaults.PersistSecurityInfo) + : true; // no password means persistable password so we don't have to munge public bool ConvertValueToBoolean(string keyName, bool defaultValue) { @@ -242,30 +185,6 @@ internal static bool ConvertValueToBooleanInternal(string keyName, string string private static bool CompareInsensitiveInvariant(string strvalue, string strconst) => (0 == StringComparer.OrdinalIgnoreCase.Compare(strvalue, strconst)); - [System.Diagnostics.Conditional("DEBUG")] - private static void DebugTraceKeyValuePair(string keyname, string keyvalue, Dictionary synonyms) - { - if (SqlClientEventSource.Log.IsAdvancedTraceOn()) - { - Debug.Assert(string.Equals(keyname, keyname?.ToLower(), StringComparison.InvariantCulture), "missing ToLower"); - string realkeyname = synonyms != null ? synonyms[keyname] : keyname; - - if (!string.Equals(KEY.Password, realkeyname, StringComparison.InvariantCultureIgnoreCase) && - !string.Equals(SYNONYM.Pwd, realkeyname, StringComparison.InvariantCultureIgnoreCase)) - { - // don't trace passwords ever! - if (keyvalue != null) - { - SqlClientEventSource.Log.AdvancedTraceEvent(" KeyName='{0}', KeyValue='{1}'", keyname, keyvalue); - } - else - { - SqlClientEventSource.Log.AdvancedTraceEvent(" KeyName='{0}'", keyname); - } - } - } - } - private static string GetKeyName(StringBuilder buffer) { int count = buffer.Length; @@ -312,6 +231,7 @@ private enum ParserState NullTermination, }; + // @TODO: This should probably be extracted into its own parser class internal static int GetKeyValuePair(string connectionString, int currentPosition, StringBuilder buffer, bool useOdbcRules, out string keyname, out string keyvalue) { int startposition = currentPosition; @@ -542,130 +462,16 @@ private static bool IsKeyNameValid(string keyname) return false; } -#if DEBUG - private static Dictionary SplitConnectionString(string connectionString, Dictionary synonyms, bool firstKey) - { - var parsetable = new Dictionary(); - Regex parser = (firstKey ? s_connectionStringRegexOdbc : s_connectionStringRegex); - - const int KeyIndex = 1, ValueIndex = 2; - Debug.Assert(KeyIndex == parser.GroupNumberFromName("key"), "wrong key index"); - Debug.Assert(ValueIndex == parser.GroupNumberFromName("value"), "wrong value index"); - - if (connectionString != null) - { - Match match = parser.Match(connectionString); - if (!match.Success || (match.Length != connectionString.Length)) - { - throw ADP.ConnectionStringSyntax(match.Length); - } - int indexValue = 0; - CaptureCollection keyvalues = match.Groups[ValueIndex].Captures; - foreach (Capture keypair in match.Groups[KeyIndex].Captures) - { - string keyname = (firstKey ? keypair.Value : keypair.Value.Replace("==", "=")).ToLower(CultureInfo.InvariantCulture); - string keyvalue = keyvalues[indexValue++].Value; - if (0 < keyvalue.Length) - { - if (!firstKey) - { - switch (keyvalue[0]) - { - case '\"': - keyvalue = keyvalue.Substring(1, keyvalue.Length - 2).Replace("\"\"", "\""); - break; - case '\'': - keyvalue = keyvalue.Substring(1, keyvalue.Length - 2).Replace("\'\'", "\'"); - break; - default: - break; - } - } - } - else - { - keyvalue = null; - } - DebugTraceKeyValuePair(keyname, keyvalue, synonyms); - string synonym; - string realkeyname = synonyms != null - ? (synonyms.TryGetValue(keyname, out synonym) ? synonym : null) - : keyname; - - if (!IsKeyNameValid(realkeyname)) - { - throw ADP.KeywordNotSupported(keyname); - } - if (!firstKey || !parsetable.ContainsKey(realkeyname)) - { - parsetable[realkeyname] = keyvalue; // last key-value pair wins (or first) - } - } - } - return parsetable; - } - - private static void ParseComparison(Dictionary parsetable, string connectionString, Dictionary synonyms, bool firstKey, Exception e) - { - try - { - var parsedvalues = SplitConnectionString(connectionString, synonyms, firstKey); - foreach (var entry in parsedvalues) - { - string keyname = entry.Key; - string value1 = entry.Value; - string value2; - bool parsetableContainsKey = parsetable.TryGetValue(keyname, out value2); - Debug.Assert(parsetableContainsKey, $"{nameof(ParseInternal)} code vs. regex mismatch keyname <{keyname}>"); - Debug.Assert(value1 == value2, $"{nameof(ParseInternal)} code vs. regex mismatch keyvalue <{value1}> <{value2}>"); - } - } - catch (ArgumentException f) - { - if (e != null) - { - string msg1 = e.Message; - string msg2 = f.Message; - - const string KeywordNotSupportedMessagePrefix = "Keyword not supported:"; - const string WrongFormatMessagePrefix = "Format of the initialization string"; - bool isEquivalent = (msg1 == msg2); - if (!isEquivalent) - { - // We also accept cases were Regex parser (debug only) reports "wrong format" and - // retail parsing code reports format exception in different location or "keyword not supported" - if (msg2.StartsWith(WrongFormatMessagePrefix, StringComparison.Ordinal)) - { - if (msg1.StartsWith(KeywordNotSupportedMessagePrefix, StringComparison.Ordinal) || msg1.StartsWith(WrongFormatMessagePrefix, StringComparison.Ordinal)) - { - isEquivalent = true; - } - } - } - Debug.Assert(isEquivalent, "ParseInternal code vs regex message mismatch: <" + msg1 + "> <" + msg2 + ">"); - } - else - { - Debug.Fail("ParseInternal code vs regex throw mismatch " + f.Message); - } - e = null; - } - if (e != null) - { - Debug.Fail("ParseInternal code threw exception vs regex mismatch"); - } - } -#endif - private static NameValuePair ParseInternal(Dictionary parsetable, string connectionString, bool buildChain, Dictionary synonyms, bool firstKey) { Debug.Assert(connectionString != null, "null connectionstring"); StringBuilder buffer = new StringBuilder(); NameValuePair localKeychain = null, keychain = null; -#if DEBUG + + #if DEBUG try { -#endif + #endif int nextStartPosition = 0; int endPosition = connectionString.Length; while (nextStartPosition < endPosition) @@ -679,9 +485,8 @@ private static NameValuePair ParseInternal(Dictionary parsetable // if (nextStartPosition != endPosition) { throw; } break; } -#if DEBUG + DebugTraceKeyValuePair(keyname, keyvalue, synonyms); -#endif Debug.Assert(IsKeyNameValid(keyname), "ParseFailure, invalid keyname"); Debug.Assert(IsValueValidInternal(keyvalue), "parse failure, invalid keyvalue"); @@ -707,7 +512,7 @@ private static NameValuePair ParseInternal(Dictionary parsetable keychain = localKeychain = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition); } } -#if DEBUG + #if DEBUG } catch (ArgumentException e) { @@ -715,7 +520,8 @@ private static NameValuePair ParseInternal(Dictionary parsetable throw; } ParseComparison(parsetable, connectionString, synonyms, firstKey, null); -#endif + #endif + return keychain; } @@ -727,8 +533,8 @@ internal NameValuePair ReplacePasswordPwd(out string constr, bool fakePassword) StringBuilder builder = new StringBuilder(_usersConnectionString.Length); for (NameValuePair current = _keyChain; current != null; current = current.Next) { - if (!string.Equals(KEY.Password, current.Name, StringComparison.InvariantCultureIgnoreCase) && - !string.Equals(SYNONYM.Pwd, current.Name, StringComparison.InvariantCultureIgnoreCase)) + if (!CompareInsensitiveInvariant(DbConnectionStringKeywords.Password, current.Name) && + !CompareInsensitiveInvariant(DbConnectionStringSynonyms.Pwd, current.Name)) { builder.Append(_usersConnectionString, copyPosition, current.Length); if (fakePassword) @@ -767,5 +573,203 @@ internal NameValuePair ReplacePasswordPwd(out string constr, bool fakePassword) constr = builder.ToString(); return head; } + + // SxS notes: + // * this method queries "DataDirectory" value from the current AppDomain. + // This string is used for to replace "!DataDirectory!" values in the connection string, it is not considered as an "exposed resource". + // * This method uses GetFullPath to validate that root path is valid, the result is not exposed out. + #if NETFRAMEWORK + [ResourceExposure(ResourceScope.None)] + [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] + #endif + internal static string ExpandDataDirectory(string keyword, string value) + { + string fullPath = null; + if (value != null && value.StartsWith(DataDirectory, StringComparison.OrdinalIgnoreCase)) + { + // find the replacement path + object rootFolderObject = AppDomain.CurrentDomain.GetData("DataDirectory"); + var rootFolderPath = (rootFolderObject as string); + if (rootFolderObject != null && rootFolderPath == null) + { + throw ADP.InvalidDataDirectory(); + } + + if (string.IsNullOrEmpty(rootFolderPath)) + { + rootFolderPath = AppDomain.CurrentDomain.BaseDirectory; + } + + var fileName = value.Substring(DataDirectory.Length); + + if (Path.IsPathRooted(fileName)) + { + fileName = fileName.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + } + + fullPath = Path.Combine(rootFolderPath, fileName); + + // verify root folder path is a real path without unexpected "..\" + if (!Path.GetFullPath(fullPath).StartsWith(rootFolderPath, StringComparison.Ordinal)) + { + throw ADP.InvalidConnectionOptionValue(keyword); + } + } + return fullPath; + } + + #region NetCore Methods + #if NET + + internal string ExpandAttachDbFileName(string replacementValue) + { + int copyPosition = 0; + + StringBuilder builder = new(_usersConnectionString.Length); + for (NameValuePair current = _keyChain; current != null; current = current.Next) + { + if (string.Equals(current.Name, DbConnectionStringKeywords.AttachDBFilename, StringComparison.InvariantCultureIgnoreCase)) + { + builder.Append($"{current.Name}={replacementValue};"); + } + else + { + builder.Append(_usersConnectionString, copyPosition, current.Length); + } + copyPosition += current.Length; + } + + return builder.ToString(); + } + + #endif + #endregion + + #region NetFx Methods + #if NETFRAMEWORK + + public string this[string keyword] => _parsetable[keyword]; + + private PermissionSet _permissionset; + + protected internal virtual PermissionSet CreatePermissionSet() => null; + + internal void DemandPermission() + { + if (_permissionset is null) + { + _permissionset = CreatePermissionSet(); + } + _permissionset.Demand(); + } + + internal bool HasBlankPassword + { + get + { + if (!ConvertValueToIntegratedSecurity()) + { + if (_parsetable.TryGetValue(DbConnectionStringKeywords.Password, out string value)) + { + return string.IsNullOrEmpty(value); + } + + if (_parsetable.TryGetValue(DbConnectionStringSynonyms.Pwd, out value)) + { + return string.IsNullOrEmpty(value); // MDAC 83097 + } + + return (_parsetable.TryGetValue(DbConnectionStringKeywords.UserID, out value) && !string.IsNullOrEmpty(value)) || + (_parsetable.TryGetValue(DbConnectionStringSynonyms.UID, out value) && !string.IsNullOrEmpty(value)); + } + return false; + } + } + + internal string ExpandKeyword(string keyword, string replacementValue) + { + // preserve duplicates, updated keyword value with replacement value + // if keyword not specified, append to end of the string + bool expanded = false; + int copyPosition = 0; + + StringBuilder builder = new(_usersConnectionString.Length); + for (NameValuePair current = _keyChain; current != null; current = current.Next) + { + if ((current.Name == keyword) && (current.Value == this[keyword])) + { + // only replace the parse end-result value instead of all values + // so that when duplicate-keywords occur other original values remain in place + AppendKeyValuePairBuilder(builder, current.Name, replacementValue); + builder.Append(';'); + expanded = true; + } + else + { + builder.Append(_usersConnectionString, copyPosition, current.Length); + } + copyPosition += current.Length; + } + + if (!expanded) + { + AppendKeyValuePairBuilder(builder, keyword, replacementValue); + } + return builder.ToString(); + } + + internal static void AppendKeyValuePairBuilder(StringBuilder builder, string keyName, string keyValue) + { + ADP.CheckArgumentNull(builder, nameof(builder)); + ADP.CheckArgumentLength(keyName, nameof(keyName)); + + if (keyName == null || !s_connectionStringValidKeyRegex.IsMatch(keyName)) + { + throw ADP.InvalidKeyname(keyName); + } + if (keyValue != null && !IsValueValidInternal(keyValue)) + { + throw ADP.InvalidValue(keyName); + } + + if ((0 < builder.Length) && (';' != builder[builder.Length - 1])) + { + builder.Append(";"); + } + + builder.Append(keyName.Replace("=", "==")); + builder.Append("="); + + if (keyValue != null) + { // else =; + if (s_connectionStringQuoteValueRegex.IsMatch(keyValue)) + { + // -> + builder.Append(keyValue); + } + else if ((-1 != keyValue.IndexOf('\"')) && (-1 == keyValue.IndexOf('\''))) + { + // -> <'val"ue'> + builder.Append('\''); + builder.Append(keyValue); + builder.Append('\''); + } + else + { + // -> <"val'ue"> + // <=value> -> <"=value"> + // <;value> -> <";value"> + // < value> -> <" value"> + // -> <"va lue"> + // -> <"va'""lue"> + builder.Append('\"'); + builder.Append(keyValue.Replace("\"", "\"\"")); + builder.Append('\"'); + } + } + } + #endif + #endregion + } } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DBConnectionString.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionString.netfx.cs similarity index 90% rename from src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DBConnectionString.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionString.netfx.cs index 66c46602c5..b302a58176 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/Common/DBConnectionString.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionString.netfx.cs @@ -2,30 +2,27 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace Microsoft.Data.Common -{ +#if NETFRAMEWORK - using System; - using System.Collections.Generic; - using System.Data; - using System.Diagnostics; - using System.Text; - using Microsoft.Data.SqlClient; +using System; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics; +using System.Text; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.SqlClient; +namespace Microsoft.Data.Common +{ + // @TODO: Theoretically this class could be replaced with SqlConnectionString. + [Serializable] // MDAC 83147 - internal sealed class DBConnectionString + internal sealed class DbConnectionString { // instances of this class are intended to be immutable, i.e readonly // used by permission classes so it is much easier to verify correctness // when not worried about the class being modified during execution - private static class KEY - { - internal const string Password = DbConnectionStringKeywords.Password; - internal const string PersistSecurityInfo = DbConnectionStringKeywords.PersistSecurityInfo; - internal const string Pwd = DbConnectionStringSynonyms.Pwd; - }; - // this class is serializable with Everett, so ugly field names can't be changed readonly private string _encryptedUsersConnectionString; @@ -50,21 +47,21 @@ private static class KEY readonly private string _encryptedActualConnectionString; #pragma warning restore 169 - internal DBConnectionString(string value, string restrictions, KeyRestrictionBehavior behavior, Dictionary synonyms, bool useOdbcRules) + internal DbConnectionString(string value, string restrictions, KeyRestrictionBehavior behavior, Dictionary synonyms, bool useOdbcRules) : this(new DbConnectionOptions(value, synonyms), restrictions, behavior, synonyms, false) { // useOdbcRules is only used to parse the connection string, not to parse restrictions because values don't apply there // the hashtable doesn't need clone since it isn't shared with anything else } - internal DBConnectionString(DbConnectionOptions connectionOptions) + internal DbConnectionString(DbConnectionOptions connectionOptions) : this(connectionOptions, (string)null, KeyRestrictionBehavior.AllowOnly, null, true) { - // used by DBDataPermission to convert from DbConnectionOptions to DBConnectionString + // used by DBDataPermission to convert from DbConnectionOptions to DbConnectionString // since backward compatibility requires Everett level classes } - private DBConnectionString(DbConnectionOptions connectionOptions, string restrictions, KeyRestrictionBehavior behavior, Dictionary synonyms, bool mustCloneDictionary) + private DbConnectionString(DbConnectionOptions connectionOptions, string restrictions, KeyRestrictionBehavior behavior, Dictionary synonyms, bool mustCloneDictionary) { // used by DBDataPermission Debug.Assert(connectionOptions != null, "null connectionOptions"); switch (behavior) @@ -100,27 +97,27 @@ private DBConnectionString(DbConnectionOptions connectionOptions, string restric // serialize out with '*' so already knows what we do. Better this way // than to treat password specially later on which causes problems. const string star = "*"; - if (_parsetable.ContainsKey(KEY.Password)) + if (_parsetable.ContainsKey(DbConnectionStringKeywords.Password)) { - _parsetable[KEY.Password] = star; + _parsetable[DbConnectionStringKeywords.Password] = star; } - if (_parsetable.ContainsKey(KEY.Pwd)) + if (_parsetable.ContainsKey(DbConnectionStringSynonyms.Pwd)) { - _parsetable[KEY.Pwd] = star; + _parsetable[DbConnectionStringSynonyms.Pwd] = star; } // replace user's password/pwd value with "*" in the linked list and build a new string _keychain = connectionOptions.ReplacePasswordPwd(out _encryptedUsersConnectionString, true); } - if (!ADP.IsEmpty(restrictions)) + if (!string.IsNullOrEmpty(restrictions)) { _restrictionValues = ParseRestrictions(restrictions, synonyms); _restrictions = restrictions; } } - private DBConnectionString(DBConnectionString connectionString, string[] restrictionValues, KeyRestrictionBehavior behavior) + private DbConnectionString(DbConnectionString connectionString, string[] restrictionValues, KeyRestrictionBehavior behavior) { // used by intersect for two equal connection strings with different restrictions _encryptedUsersConnectionString = connectionString._encryptedUsersConnectionString; @@ -168,7 +165,7 @@ internal string Restrictions StringBuilder builder = new StringBuilder(); for (int i = 0; i < restrictionValues.Length; ++i) { - if (!ADP.IsEmpty(restrictionValues[i])) + if (!string.IsNullOrEmpty(restrictionValues[i])) { builder.Append(restrictionValues[i]); builder.Append("=;"); @@ -197,7 +194,7 @@ internal bool ContainsKey(string keyword) return _parsetable.ContainsKey(keyword); } - internal DBConnectionString Intersect(DBConnectionString entry) + internal DbConnectionString Intersect(DbConnectionString entry) { KeyRestrictionBehavior behavior = _behavior; string[] restrictionValues = null; @@ -286,10 +283,10 @@ internal DBConnectionString Intersect(DBConnectionString entry) } // verify _hasPassword & _parsetable are in sync between Everett/Whidbey - Debug.Assert(!_hasPassword || ContainsKey(KEY.Password) || ContainsKey(KEY.Pwd), "OnDeserialized password mismatch this"); - Debug.Assert(entry == null || !entry._hasPassword || entry.ContainsKey(KEY.Password) || entry.ContainsKey(KEY.Pwd), "OnDeserialized password mismatch entry"); + Debug.Assert(!_hasPassword || ContainsKey(DbConnectionStringKeywords.Password) || ContainsKey(DbConnectionStringSynonyms.Pwd), "OnDeserialized password mismatch this"); + Debug.Assert(entry == null || !entry._hasPassword || entry.ContainsKey(DbConnectionStringKeywords.Password) || entry.ContainsKey(DbConnectionStringSynonyms.Pwd), "OnDeserialized password mismatch entry"); - DBConnectionString value = new DBConnectionString(this, restrictionValues, behavior); + DbConnectionString value = new DbConnectionString(this, restrictionValues, behavior); ValidateCombinedSet(this, value); ValidateCombinedSet(entry, value); @@ -297,7 +294,7 @@ internal DBConnectionString Intersect(DBConnectionString entry) } [Conditional("DEBUG")] - private void ValidateCombinedSet(DBConnectionString componentSet, DBConnectionString combinedSet) + private void ValidateCombinedSet(DbConnectionString componentSet, DbConnectionString combinedSet) { Debug.Assert(combinedSet != null, "The combined connection string should not be null"); if ((componentSet != null) && (combinedSet._restrictionValues != null) && (componentSet._restrictionValues != null)) @@ -370,10 +367,10 @@ private bool IsRestrictedKeyword(string key) return (_restrictionValues == null || (0 > Array.BinarySearch(_restrictionValues, key, StringComparer.Ordinal))); } - internal bool IsSupersetOf(DBConnectionString entry) + internal bool IsSupersetOf(DbConnectionString entry) { - Debug.Assert(!_hasPassword || ContainsKey(KEY.Password) || ContainsKey(KEY.Pwd), "OnDeserialized password mismatch this"); - Debug.Assert(!entry._hasPassword || entry.ContainsKey(KEY.Password) || entry.ContainsKey(KEY.Pwd), "OnDeserialized password mismatch entry"); + Debug.Assert(!_hasPassword || ContainsKey(DbConnectionStringKeywords.Password) || ContainsKey(DbConnectionStringSynonyms.Pwd), "OnDeserialized password mismatch this"); + Debug.Assert(!entry._hasPassword || entry.ContainsKey(DbConnectionStringKeywords.Password) || entry.ContainsKey(DbConnectionStringSynonyms.Pwd), "OnDeserialized password mismatch entry"); switch (_behavior) { @@ -480,10 +477,10 @@ static private string[] NoDuplicateUnion(string[] a, string[] b) return restrictionValues; } - private static string[] ParseRestrictions(string restrictions, Dictionary synonyms) + private static string[] ParseRestrictions(string restrictions, IReadOnlyDictionary synonyms) { #if DEBUG - SqlClientEventSource.Log.TryAdvancedTraceEvent(" Restrictions='{0}'", restrictions); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" Restrictions='{0}'", restrictions); #endif List restrictionValues = new List(); StringBuilder buffer = new StringBuilder(restrictions.Length); @@ -496,13 +493,13 @@ private static string[] ParseRestrictions(string restrictions, Dictionary KeyName='{0}'", keyname); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" KeyName='{0}'", keyname); #endif string realkeyname = synonyms != null ? (string)synonyms[keyname] : keyname; // MDAC 85144 - if (ADP.IsEmpty(realkeyname)) + if (string.IsNullOrEmpty(realkeyname)) { throw ADP.KeywordNotSupported(keyname); } @@ -559,11 +556,13 @@ private static void Verify(string[] restrictionValues) { for (int i = 1; i < restrictionValues.Length; ++i) { - Debug.Assert(!ADP.IsEmpty(restrictionValues[i - 1]), "empty restriction"); - Debug.Assert(!ADP.IsEmpty(restrictionValues[i]), "empty restriction"); + Debug.Assert(!string.IsNullOrEmpty(restrictionValues[i - 1]), "empty restriction"); + Debug.Assert(!string.IsNullOrEmpty(restrictionValues[i]), "empty restriction"); Debug.Assert(0 >= StringComparer.Ordinal.Compare(restrictionValues[i - 1], restrictionValues[i])); } } } } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringDefaults.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringDefaults.cs new file mode 100644 index 0000000000..41a8058b5c --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringDefaults.cs @@ -0,0 +1,64 @@ +// 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; + +namespace Microsoft.Data.Common.ConnectionString +{ + internal static class DbConnectionStringDefaults + { + internal const ApplicationIntent ApplicationIntent = Microsoft.Data.SqlClient.ApplicationIntent.ReadWrite; + internal const string ApplicationName = + #if NETFRAMEWORK + "Framework Microsoft SqlClient Data Provider"; + #else + "Core Microsoft SqlClient Data Provider"; + #endif + internal const string AttachDBFilename = ""; + internal const SqlConnectionAttestationProtocol AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified; + internal static readonly SqlAuthenticationMethod Authentication = SqlAuthenticationMethod.NotSpecified; + internal const SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Disabled; + internal const int CommandTimeout = 30; + internal const int ConnectRetryCount = 1; + internal const int ConnectRetryInterval = 10; + internal const int ConnectTimeout = 15; + internal const bool ContextConnection = false; + internal const string CurrentLanguage = ""; + internal const string DataSource = ""; + internal const string EnclaveAttestationUrl = ""; + internal static readonly SqlConnectionEncryptOption Encrypt = SqlConnectionEncryptOption.Mandatory; + internal const bool Enlist = true; + internal const string FailoverPartner = ""; + internal const string FailoverPartnerSPN = ""; + internal const string HostNameInCertificate = ""; + internal const string InitialCatalog = ""; + internal const bool IntegratedSecurity = false; + internal const SqlConnectionIPAddressPreference IPAddressPreference = SqlConnectionIPAddressPreference.IPv4First; + internal const int LoadBalanceTimeout = 0; // default of 0 means don't use + internal const int MaxPoolSize = 100; + internal const int MinPoolSize = 0; + internal const bool MultipleActiveResultSets = false; + internal const bool MultiSubnetFailover = false; + internal const int PacketSize = 8000; + internal const string Password = ""; + internal const bool PersistSecurityInfo = false; + internal const PoolBlockingPeriod PoolBlockingPeriod = SqlClient.PoolBlockingPeriod.Auto; + internal const bool Pooling = true; + internal const bool Replication = false; + internal const string ServerCertificate = ""; + internal const string ServerSPN = ""; + internal const string TransactionBinding = "Implicit Unbind"; + internal const bool TrustServerCertificate = false; + internal const string TypeSystemVersion = "Latest"; + internal const string UserID = ""; + internal const bool UserInstance = false; + internal const string WorkstationID = ""; + + #if NETFRAMEWORK + internal const bool ConnectionReset = true; + internal static readonly bool TransparentNetworkIPResolution = !LocalAppContextSwitches.DisableTnirByDefault; + internal const string NetworkLibrary = ""; + #endif + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringKeywords.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringKeywords.cs new file mode 100644 index 0000000000..65cb781fa1 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringKeywords.cs @@ -0,0 +1,72 @@ +// 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. + +namespace Microsoft.Data.Common.ConnectionString +{ + internal static class DbConnectionStringKeywords + { + // SqlClient + internal const string ApplicationIntent = "Application Intent"; + internal const string ApplicationName = "Application Name"; + internal const string AttachDBFilename = "AttachDbFilename"; + internal const string AttestationProtocol = "Attestation Protocol"; + internal const string Authentication = "Authentication"; + internal const string ColumnEncryptionSetting = "Column Encryption Setting"; + internal const string CommandTimeout = "Command Timeout"; + internal const string ConnectionReset = "Connection Reset"; + internal const string ConnectRetryCount = "Connect Retry Count"; + internal const string ConnectRetryInterval = "Connect Retry Interval"; + internal const string ConnectTimeout = "Connect Timeout"; + internal const string ContextConnection = "Context Connection"; + internal const string CurrentLanguage = "Current Language"; + internal const string DataSource = "Data Source"; + internal const string EnclaveAttestationUrl = "Enclave Attestation Url"; + internal const string Encrypt = "Encrypt"; + internal const string Enlist = "Enlist"; + internal const string FailoverPartner = "Failover Partner"; + internal const string FailoverPartnerSPN = "Failover Partner SPN"; + internal const string HostNameInCertificate = "Host Name In Certificate"; + internal const string InitialCatalog = "Initial Catalog"; + internal const string IntegratedSecurity = "Integrated Security"; + internal const string IPAddressPreference = "IP Address Preference"; + internal const string LoadBalanceTimeout = "Load Balance Timeout"; + internal const string MaxPoolSize = "Max Pool Size"; + internal const string MinPoolSize = "Min Pool Size"; + internal const string MultipleActiveResultSets = "Multiple Active Result Sets"; + internal const string MultiSubnetFailover = "Multi Subnet Failover"; + internal const string NetworkLibrary = "Network Library"; + internal const string PacketSize = "Packet Size"; + internal const string Password = "Password"; + internal const string PersistSecurityInfo = "Persist Security Info"; + internal const string PoolBlockingPeriod = "Pool Blocking Period"; + internal const string Pooling = "Pooling"; + internal const string Replication = "Replication"; + internal const string ServerCertificate = "Server Certificate"; + internal const string ServerSPN = "Server SPN"; + internal const string TransactionBinding = "Transaction Binding"; + internal const string TransparentNetworkIPResolution = "Transparent Network IP Resolution"; + internal const string TrustServerCertificate = "Trust Server Certificate"; + internal const string TypeSystemVersion = "Type System Version"; + internal const string UserID = "User ID"; + internal const string UserInstance = "User Instance"; + internal const string WorkstationID = "Workstation ID"; + + #if NETFRAMEWORK + // Odbc + internal const string Driver = "Driver"; + internal const string Dsn = "Dsn"; + internal const string FileDsn = "FileDsn"; + internal const string SaveFile = "SaveFile"; + + // OleDb + internal const string FileName = "File Name"; + internal const string OleDbServices = "OLE DB Services"; + internal const string Provider = "Provider"; + + // OracleClient + internal const string Unicode = "Unicode"; + internal const string OmitOracleConnectionName = "Omit Oracle Connection Name"; + #endif + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringSynonyms.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringSynonyms.cs new file mode 100644 index 0000000000..f7d8881544 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringSynonyms.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Data.Common.ConnectionString +{ + internal static class DbConnectionStringSynonyms + { + //internal const string TransparentNetworkIPResolution = TRANSPARENTNETWORKIPRESOLUTION; + internal const string TRANSPARENTNETWORKIPRESOLUTION = "transparentnetworkipresolution"; + + //internal const string ApplicationName = APP; + internal const string APP = "app"; + + // internal const string IPAddressPreference = IPADDRESSPREFERENCE; + internal const string IPADDRESSPREFERENCE = "ipaddresspreference"; + + //internal const string ApplicationIntent = APPLICATIONINTENT; + internal const string APPLICATIONINTENT = "applicationintent"; + + //internal const string AttachDBFilename = EXTENDEDPROPERTIES + "," + INITIALFILENAME; + internal const string EXTENDEDPROPERTIES = "extended properties"; + internal const string INITIALFILENAME = "initial file name"; + + // internal const string HostNameInCertificate = HOSTNAMEINCERTIFICATE; + internal const string HOSTNAMEINCERTIFICATE = "hostnameincertificate"; + + // internal const string ServerCertificate = SERVERCERTIFICATE; + internal const string SERVERCERTIFICATE = "servercertificate"; + + //internal const string ConnectTimeout = CONNECTIONTIMEOUT + "," +TIMEOUT; + internal const string CONNECTIONTIMEOUT = "connection timeout"; + internal const string TIMEOUT = "timeout"; + + //internal const string ConnectRetryCount = CONNECTRETRYCOUNT; + internal const string CONNECTRETRYCOUNT = "connectretrycount"; + + //internal const string ConnectRetryInterval = CONNECTRETRYINTERVAL; + internal const string CONNECTRETRYINTERVAL = "connectretryinterval"; + + //internal const string CurrentLanguage = LANGUAGE; + internal const string LANGUAGE = "language"; + + //internal const string OraDataSource = SERVER; + //internal const string SqlDataSource = ADDR + "," + ADDRESS + "," + SERVER + "," + NETWORKADDRESS; + internal const string ADDR = "addr"; + internal const string ADDRESS = "address"; + internal const string SERVER = "server"; + internal const string NETWORKADDRESS = "network address"; + + //internal const string InitialCatalog = DATABASE; + internal const string DATABASE = "database"; + + //internal const string IntegratedSecurity = TRUSTEDCONNECTION; + internal const string TRUSTEDCONNECTION = "trusted_connection"; // underscore introduced in everett + + //internal const string LoadBalanceTimeout = ConnectionLifetime; + internal const string ConnectionLifetime = "connection lifetime"; + + //internal const string MultipleActiveResultSets = MULTIPLEACTIVERESULTSETS; + internal const string MULTIPLEACTIVERESULTSETS = "multipleactiveresultsets"; + + //internal const string MultiSubnetFailover = MULTISUBNETFAILOVER; + internal const string MULTISUBNETFAILOVER = "multisubnetfailover"; + + //internal const string NetworkLibrary = NET + "," + NETWORK; + internal const string NET = "net"; + internal const string NETWORK = "network"; + + //internal const string PoolBlockingPeriod = POOLBLOCKINGPERIOD; + internal const string POOLBLOCKINGPERIOD = "poolblockingperiod"; + + //internal const string Password = Pwd; + internal const string Pwd = "pwd"; + + //internal const string PersistSecurityInfo = PERSISTSECURITYINFO; + internal const string PERSISTSECURITYINFO = "persistsecurityinfo"; + + //internal const string TrustServerCertificate = TRUSTSERVERCERTIFICATE; + internal const string TRUSTSERVERCERTIFICATE = "trustservercertificate"; + + //internal const string UserID = UID + "," + User; + internal const string UID = "uid"; + internal const string User = "user"; + + //internal const string WorkstationID = WSID; + internal const string WSID = "wsid"; + + //internal const string server SPNs + internal const string ServerSPN = "ServerSPN"; + internal const string FailoverPartnerSPN = "FailoverPartnerSPN"; + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringUtilities.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringUtilities.cs new file mode 100644 index 0000000000..927b2e62a6 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/DbConnectionStringUtilities.cs @@ -0,0 +1,592 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.SqlClient; + +namespace Microsoft.Data.Common +{ + internal static class DbConnectionStringUtilities + { + internal static bool ConvertToBoolean(object value) + { + Debug.Assert(value != null, "ConvertToBoolean(null)"); + if (value is string svalue) + { + if (StringComparer.OrdinalIgnoreCase.Equals(svalue, "true") || StringComparer.OrdinalIgnoreCase.Equals(svalue, "yes")) + { + return true; + } + else if (StringComparer.OrdinalIgnoreCase.Equals(svalue, "false") || StringComparer.OrdinalIgnoreCase.Equals(svalue, "no")) + { + return false; + } + else + { + string tmp = svalue.Trim(); // Remove leading & trailing white space. + if (StringComparer.OrdinalIgnoreCase.Equals(tmp, "true") || StringComparer.OrdinalIgnoreCase.Equals(tmp, "yes")) + { + return true; + } + else if (StringComparer.OrdinalIgnoreCase.Equals(tmp, "false") || StringComparer.OrdinalIgnoreCase.Equals(tmp, "no")) + { + return false; + } + } + return bool.Parse(svalue); + } + try + { + return Convert.ToBoolean(value, CultureInfo.InvariantCulture); + } + catch (InvalidCastException e) + { + throw ADP.ConvertFailed(value.GetType(), typeof(bool), e); + } + } + + internal static bool ConvertToIntegratedSecurity(object value) + { + Debug.Assert(value != null, "ConvertToIntegratedSecurity(null)"); + if (value is string svalue) + { + if (StringComparer.OrdinalIgnoreCase.Equals(svalue, "sspi") || StringComparer.OrdinalIgnoreCase.Equals(svalue, "true") || StringComparer.OrdinalIgnoreCase.Equals(svalue, "yes")) + return true; + else if (StringComparer.OrdinalIgnoreCase.Equals(svalue, "false") || StringComparer.OrdinalIgnoreCase.Equals(svalue, "no")) + return false; + else + { + string tmp = svalue.Trim(); // Remove leading & trailing white space. + if (StringComparer.OrdinalIgnoreCase.Equals(tmp, "sspi") || StringComparer.OrdinalIgnoreCase.Equals(tmp, "true") || StringComparer.OrdinalIgnoreCase.Equals(tmp, "yes")) + return true; + else if (StringComparer.OrdinalIgnoreCase.Equals(tmp, "false") || StringComparer.OrdinalIgnoreCase.Equals(tmp, "no")) + return false; + } + return bool.Parse(svalue); + } + try + { + return Convert.ToBoolean(value, CultureInfo.InvariantCulture); + } + catch (InvalidCastException e) + { + throw ADP.ConvertFailed(value.GetType(), typeof(bool), e); + } + } + + internal static int ConvertToInt32(object value) + { + try + { + return Convert.ToInt32(value, CultureInfo.InvariantCulture); + } + catch (InvalidCastException e) + { + throw ADP.ConvertFailed(value.GetType(), typeof(int), e); + } + } + + internal static string ConvertToString(object value) + { + try + { + return Convert.ToString(value, CultureInfo.InvariantCulture); + } + catch (InvalidCastException e) + { + throw ADP.ConvertFailed(value.GetType(), typeof(string), e); + } + } + + internal static bool TryConvertToApplicationIntent(string value, out ApplicationIntent result) + { + Debug.Assert(Enum.GetNames(typeof(ApplicationIntent)).Length == 2, "ApplicationIntent enum has changed, update needed"); + Debug.Assert(value != null, "TryConvertToApplicationIntent(null,...)"); + + if (StringComparer.OrdinalIgnoreCase.Equals(value, nameof(ApplicationIntent.ReadOnly))) + { + result = ApplicationIntent.ReadOnly; + return true; + } + else if (StringComparer.OrdinalIgnoreCase.Equals(value, nameof(ApplicationIntent.ReadWrite))) + { + result = ApplicationIntent.ReadWrite; + return true; + } + else + { + result = DbConnectionStringDefaults.ApplicationIntent; + return false; + } + } + + internal static bool IsValidApplicationIntentValue(ApplicationIntent value) + { + Debug.Assert(Enum.GetNames(typeof(ApplicationIntent)).Length == 2, "ApplicationIntent enum has changed, update needed"); + return value == ApplicationIntent.ReadOnly || value == ApplicationIntent.ReadWrite; + } + + internal static string ApplicationIntentToString(ApplicationIntent value) + { + Debug.Assert(IsValidApplicationIntentValue(value)); + if (value == ApplicationIntent.ReadOnly) + { + return nameof(ApplicationIntent.ReadOnly); + } + else + { + return nameof(ApplicationIntent.ReadWrite); + } + } + + /// + /// This method attempts to convert the given value tp ApplicationIntent enum. The algorithm is: + /// * if the value is from type string, it will be matched against ApplicationIntent enum names only, using ordinal, case-insensitive comparer + /// * if the value is from type ApplicationIntent, it will be used as is + /// * if the value is from integral type (SByte, Int16, Int32, Int64, Byte, UInt16, UInt32, or UInt64), it will be converted to enum + /// * if the value is another enum or any other type, it will be blocked with an appropriate ArgumentException + /// + /// in any case above, if the converted value is out of valid range, the method raises ArgumentOutOfRangeException. + /// + /// application intent value in the valid range + internal static ApplicationIntent ConvertToApplicationIntent(string keyword, object value) + { + Debug.Assert(value != null, "ConvertToApplicationIntent(null)"); + if (value is string sValue) + { + // We could use Enum.TryParse here, but it accepts value combinations like + // "ReadOnly, ReadWrite" which are unwelcome here + // Also, Enum.TryParse is 100x slower than plain StringComparer.OrdinalIgnoreCase.Equals method. + + if (TryConvertToApplicationIntent(sValue, out ApplicationIntent result)) + { + return result; + } + + // try again after remove leading & trailing whitespaces. + sValue = sValue.Trim(); + if (TryConvertToApplicationIntent(sValue, out result)) + { + return result; + } + + // string values must be valid + throw ADP.InvalidConnectionOptionValue(keyword); + } + else + { + // the value is not string, try other options + ApplicationIntent eValue; + + if (value is ApplicationIntent intent) + { + // quick path for the most common case + eValue = intent; + } + else if (value.GetType().IsEnum) + { + // explicitly block scenarios in which user tries to use wrong enum types, like: + // builder["ApplicationIntent"] = EnvironmentVariableTarget.Process; + // workaround: explicitly cast non-ApplicationIntent enums to int + throw ADP.ConvertFailed(value.GetType(), typeof(ApplicationIntent), null); + } + else + { + try + { + // Enum.ToObject allows only integral and enum values (enums are blocked above), raising ArgumentException for the rest + eValue = (ApplicationIntent)Enum.ToObject(typeof(ApplicationIntent), value); + } + catch (ArgumentException e) + { + // to be consistent with the messages we send in case of wrong type usage, replace + // the error with our exception, and keep the original one as inner one for troubleshooting + throw ADP.ConvertFailed(value.GetType(), typeof(ApplicationIntent), e); + } + } + + // ensure value is in valid range + if (IsValidApplicationIntentValue(eValue)) + { + return eValue; + } + else + { + throw ADP.InvalidEnumerationValue(typeof(ApplicationIntent), (int)eValue); + } + } + } + + const string SqlPasswordString = "Sql Password"; + [Obsolete("ActiveDirectoryPassword is deprecated.")] + const string ActiveDirectoryPasswordString = "Active Directory Password"; + const string ActiveDirectoryIntegratedString = "Active Directory Integrated"; + const string ActiveDirectoryInteractiveString = "Active Directory Interactive"; + const string ActiveDirectoryServicePrincipalString = "Active Directory Service Principal"; + const string ActiveDirectoryDeviceCodeFlowString = "Active Directory Device Code Flow"; + internal const string ActiveDirectoryManagedIdentityString = "Active Directory Managed Identity"; + internal const string ActiveDirectoryMSIString = "Active Directory MSI"; + internal const string ActiveDirectoryDefaultString = "Active Directory Default"; + internal const string ActiveDirectoryWorkloadIdentityString = "Active Directory Workload Identity"; + +#if DEBUG + private static readonly string[] s_supportedAuthenticationModes = + { + "NotSpecified", + "SqlPassword", + "ActiveDirectoryPassword", + "ActiveDirectoryIntegrated", + "ActiveDirectoryInteractive", + "ActiveDirectoryServicePrincipal", + "ActiveDirectoryDeviceCodeFlow", + "ActiveDirectoryManagedIdentity", + "ActiveDirectoryMSI", + "ActiveDirectoryDefault", + "ActiveDirectoryWorkloadIdentity", + }; + + private static bool IsValidAuthenticationMethodEnum() + { + string[] names = Enum.GetNames(typeof(SqlAuthenticationMethod)); + int l = s_supportedAuthenticationModes.Length; + bool listValid; + if (listValid = names.Length == l) + { + for (int i = 0; i < l; i++) + { + if (string.Compare(s_supportedAuthenticationModes[i], names[i], StringComparison.Ordinal) != 0) + { + listValid = false; + } + } + } + return listValid; + } +#endif + + internal static bool TryConvertToAuthenticationType(string value, out SqlAuthenticationMethod result) + { +#if DEBUG + Debug.Assert(IsValidAuthenticationMethodEnum(), "SqlAuthenticationMethod enum has changed, update needed"); +#endif + bool isSuccess = false; + + if (StringComparer.InvariantCultureIgnoreCase.Equals(value, SqlPasswordString) + || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.SqlPassword, CultureInfo.InvariantCulture))) + { + result = SqlAuthenticationMethod.SqlPassword; + isSuccess = true; + } + #pragma warning disable 0618 + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryPasswordString) + || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryPassword, CultureInfo.InvariantCulture))) + { + result = SqlAuthenticationMethod.ActiveDirectoryPassword; + #pragma warning restore 0618 + isSuccess = true; + } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryIntegratedString) + || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryIntegrated, CultureInfo.InvariantCulture))) + { + result = SqlAuthenticationMethod.ActiveDirectoryIntegrated; + isSuccess = true; + } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryInteractiveString) + || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryInteractive, CultureInfo.InvariantCulture))) + { + result = SqlAuthenticationMethod.ActiveDirectoryInteractive; + isSuccess = true; + } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryServicePrincipalString) + || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, CultureInfo.InvariantCulture))) + { + result = SqlAuthenticationMethod.ActiveDirectoryServicePrincipal; + isSuccess = true; + } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryDeviceCodeFlowString) + || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, CultureInfo.InvariantCulture))) + { + result = SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow; + isSuccess = true; + } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryManagedIdentityString) + || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, CultureInfo.InvariantCulture))) + { + result = SqlAuthenticationMethod.ActiveDirectoryManagedIdentity; + isSuccess = true; + } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryMSIString) + || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryMSI, CultureInfo.InvariantCulture))) + { + result = SqlAuthenticationMethod.ActiveDirectoryMSI; + isSuccess = true; + } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryDefaultString) + || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryDefault, CultureInfo.InvariantCulture))) + { + result = SqlAuthenticationMethod.ActiveDirectoryDefault; + isSuccess = true; + } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryWorkloadIdentityString) + || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity, CultureInfo.InvariantCulture))) + { + result = SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity; + isSuccess = true; + } + else + { + result = DbConnectionStringDefaults.Authentication; + } + return isSuccess; + } + + /// + /// Convert a string value to the corresponding SqlConnectionColumnEncryptionSetting. + /// + /// + /// + /// + internal static bool TryConvertToColumnEncryptionSetting(string value, out SqlConnectionColumnEncryptionSetting result) + { + bool isSuccess = false; + + if (StringComparer.InvariantCultureIgnoreCase.Equals(value, nameof(SqlConnectionColumnEncryptionSetting.Enabled))) + { + result = SqlConnectionColumnEncryptionSetting.Enabled; + isSuccess = true; + } + else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, nameof(SqlConnectionColumnEncryptionSetting.Disabled))) + { + result = SqlConnectionColumnEncryptionSetting.Disabled; + isSuccess = true; + } + else + { + result = DbConnectionStringDefaults.ColumnEncryptionSetting; + } + + return isSuccess; + } + + /// + /// Is it a valid connection level column encryption setting ? + /// + /// + /// + internal static bool IsValidColumnEncryptionSetting(SqlConnectionColumnEncryptionSetting value) + { + Debug.Assert(Enum.GetNames(typeof(SqlConnectionColumnEncryptionSetting)).Length == 2, "SqlConnectionColumnEncryptionSetting enum has changed, update needed"); + return value == SqlConnectionColumnEncryptionSetting.Enabled || value == SqlConnectionColumnEncryptionSetting.Disabled; + } + + /// + /// Convert connection level column encryption setting value to string. + /// + /// + /// + internal static string ColumnEncryptionSettingToString(SqlConnectionColumnEncryptionSetting value) + { + Debug.Assert(IsValidColumnEncryptionSetting(value), "value is not a valid connection level column encryption setting."); + + return value switch + { + SqlConnectionColumnEncryptionSetting.Enabled => nameof(SqlConnectionColumnEncryptionSetting.Enabled), + SqlConnectionColumnEncryptionSetting.Disabled => nameof(SqlConnectionColumnEncryptionSetting.Disabled), + _ => null, + }; + } + + internal static bool IsValidAuthenticationTypeValue(SqlAuthenticationMethod value) + { + Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 11, "SqlAuthenticationMethod enum has changed, update needed"); + return value == SqlAuthenticationMethod.SqlPassword + #pragma warning disable 0618 + || value == SqlAuthenticationMethod.ActiveDirectoryPassword + #pragma warning restore 0618 + || value == SqlAuthenticationMethod.ActiveDirectoryIntegrated + || value == SqlAuthenticationMethod.ActiveDirectoryInteractive + || value == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal + || value == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow + || value == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity + || value == SqlAuthenticationMethod.ActiveDirectoryMSI + || value == SqlAuthenticationMethod.ActiveDirectoryDefault + || value == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity + || value == SqlAuthenticationMethod.NotSpecified; + } + + internal static string AuthenticationTypeToString(SqlAuthenticationMethod value) + { + Debug.Assert(IsValidAuthenticationTypeValue(value)); + + return value switch + { + SqlAuthenticationMethod.SqlPassword => SqlPasswordString, + #pragma warning disable 0618 + SqlAuthenticationMethod.ActiveDirectoryPassword => ActiveDirectoryPasswordString, + #pragma warning restore 0618 + SqlAuthenticationMethod.ActiveDirectoryIntegrated => ActiveDirectoryIntegratedString, + SqlAuthenticationMethod.ActiveDirectoryInteractive => ActiveDirectoryInteractiveString, + SqlAuthenticationMethod.ActiveDirectoryServicePrincipal => ActiveDirectoryServicePrincipalString, + SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow => ActiveDirectoryDeviceCodeFlowString, + SqlAuthenticationMethod.ActiveDirectoryManagedIdentity => ActiveDirectoryManagedIdentityString, + SqlAuthenticationMethod.ActiveDirectoryMSI => ActiveDirectoryMSIString, + SqlAuthenticationMethod.ActiveDirectoryDefault => ActiveDirectoryDefaultString, + SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity => ActiveDirectoryWorkloadIdentityString, + _ => null + }; + } + + internal static SqlAuthenticationMethod ConvertToAuthenticationType(string keyword, object value) + { + if (value == null) + { + return DbConnectionStringDefaults.Authentication; + } + + if (value is string sValue) + { + if (TryConvertToAuthenticationType(sValue, out SqlAuthenticationMethod result)) + { + return result; + } + + // try again after remove leading & trailing whitespaces. + sValue = sValue.Trim(); + if (TryConvertToAuthenticationType(sValue, out result)) + { + return result; + } + + // string values must be valid + throw ADP.InvalidConnectionOptionValue(keyword); + } + else + { + // the value is not string, try other options + SqlAuthenticationMethod eValue; + + if (value is SqlAuthenticationMethod method) + { + // quick path for the most common case + eValue = method; + } + else if (value.GetType().IsEnum) + { + // explicitly block scenarios in which user tries to use wrong enum types, like: + // builder["ApplicationIntent"] = EnvironmentVariableTarget.Process; + // workaround: explicitly cast non-ApplicationIntent enums to int + throw ADP.ConvertFailed(value.GetType(), typeof(SqlAuthenticationMethod), null); + } + else + { + try + { + // Enum.ToObject allows only integral and enum values (enums are blocked above), raising ArgumentException for the rest + eValue = (SqlAuthenticationMethod)Enum.ToObject(typeof(SqlAuthenticationMethod), value); + } + catch (ArgumentException e) + { + // to be consistent with the messages we send in case of wrong type usage, replace + // the error with our exception, and keep the original one as inner one for troubleshooting + throw ADP.ConvertFailed(value.GetType(), typeof(SqlAuthenticationMethod), e); + } + } + + // ensure value is in valid range + if (IsValidAuthenticationTypeValue(eValue)) + { + return eValue; + } + else + { + throw ADP.InvalidEnumerationValue(typeof(SqlAuthenticationMethod), (int)eValue); + } + } + } + + /// + /// Convert the provided value to a SqlConnectionColumnEncryptionSetting. + /// + /// + /// + /// + internal static SqlConnectionColumnEncryptionSetting ConvertToColumnEncryptionSetting(string keyword, object value) + { + if (value == null) + { + return DbConnectionStringDefaults.ColumnEncryptionSetting; + } + + if (value is string sValue) + { + if (TryConvertToColumnEncryptionSetting(sValue, out SqlConnectionColumnEncryptionSetting result)) + { + return result; + } + + // try again after remove leading & trailing whitespaces. + sValue = sValue.Trim(); + if (TryConvertToColumnEncryptionSetting(sValue, out result)) + { + return result; + } + + // string values must be valid + throw ADP.InvalidConnectionOptionValue(keyword); + } + else + { + // the value is not string, try other options + SqlConnectionColumnEncryptionSetting eValue; + + if (value is SqlConnectionColumnEncryptionSetting setting) + { + // quick path for the most common case + eValue = setting; + } + else if (value.GetType().IsEnum) + { + // explicitly block scenarios in which user tries to use wrong enum types, like: + // builder["SqlConnectionColumnEncryptionSetting"] = EnvironmentVariableTarget.Process; + // workaround: explicitly cast non-SqlConnectionColumnEncryptionSetting enums to int + throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionColumnEncryptionSetting), null); + } + else + { + try + { + // Enum.ToObject allows only integral and enum values (enums are blocked above), raising ArgumentException for the rest + eValue = (SqlConnectionColumnEncryptionSetting)Enum.ToObject(typeof(SqlConnectionColumnEncryptionSetting), value); + } + catch (ArgumentException e) + { + // to be consistent with the messages we send in case of wrong type usage, replace + // the error with our exception, and keep the original one as inner one for troubleshooting + throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionColumnEncryptionSetting), e); + } + } + + // ensure value is in valid range + if (IsValidColumnEncryptionSetting(eValue)) + { + return eValue; + } + else + { + throw ADP.InvalidEnumerationValue(typeof(SqlConnectionColumnEncryptionSetting), (int)eValue); + } + } + } + } + + + + + + +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/IpAddressPreferenceUtilities.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/IpAddressPreferenceUtilities.cs new file mode 100644 index 0000000000..a9e59dfe2b --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/IpAddressPreferenceUtilities.cs @@ -0,0 +1,115 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Microsoft.Data.SqlClient; + +namespace Microsoft.Data.Common.ConnectionString +{ + internal static class IpAddressPreferenceUtilities + { + /// + /// IP Address Preference. + /// + private readonly static Dictionary s_preferenceNames = new(StringComparer.InvariantCultureIgnoreCase); + + static IpAddressPreferenceUtilities() + { + foreach (SqlConnectionIPAddressPreference item in Enum.GetValues(typeof(SqlConnectionIPAddressPreference))) + { + s_preferenceNames.Add(item.ToString(), item); + } + } + + /// + /// Convert a string value to the corresponding IPAddressPreference. + /// + /// The string representation of the enumeration name to convert. + /// When this method returns, `result` contains an object of type `SqlConnectionIPAddressPreference` whose value is represented by `value` if the operation succeeds. + /// If the parse operation fails, `result` contains the default value of the `SqlConnectionIPAddressPreference` type. + /// `true` if the value parameter was converted successfully; otherwise, `false`. + internal static bool TryConvertToIPAddressPreference(string value, out SqlConnectionIPAddressPreference result) + { + if (!s_preferenceNames.TryGetValue(value, out result)) + { + result = DbConnectionStringDefaults.IPAddressPreference; + return false; + } + return true; + } + + /// + /// Verifies if the `value` is defined in the expected Enum. + /// + internal static bool IsValidIPAddressPreference(SqlConnectionIPAddressPreference value) + => value == SqlConnectionIPAddressPreference.IPv4First + || value == SqlConnectionIPAddressPreference.IPv6First + || value == SqlConnectionIPAddressPreference.UsePlatformDefault; + + internal static string IPAddressPreferenceToString(SqlConnectionIPAddressPreference value) + => Enum.GetName(typeof(SqlConnectionIPAddressPreference), value); + + internal static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value) + { + if (value is null) + { + return DbConnectionStringDefaults.IPAddressPreference; // IPv4First + } + + if (value is string sValue) + { + // try again after remove leading & trailing whitespaces. + sValue = sValue.Trim(); + if (TryConvertToIPAddressPreference(sValue, out SqlConnectionIPAddressPreference result)) + { + return result; + } + + // string values must be valid + throw ADP.InvalidConnectionOptionValue(keyword); + } + else + { + // the value is not string, try other options + SqlConnectionIPAddressPreference eValue; + + if (value is SqlConnectionIPAddressPreference preference) + { + eValue = preference; + } + else if (value.GetType().IsEnum) + { + // explicitly block scenarios in which user tries to use wrong enum types, like: + // builder["SqlConnectionIPAddressPreference"] = EnvironmentVariableTarget.Process; + // workaround: explicitly cast non-SqlConnectionIPAddressPreference enums to int + throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionIPAddressPreference), null); + } + else + { + try + { + // Enum.ToObject allows only integral and enum values (enums are blocked above), raising ArgumentException for the rest + eValue = (SqlConnectionIPAddressPreference)Enum.ToObject(typeof(SqlConnectionIPAddressPreference), value); + } + catch (ArgumentException e) + { + // to be consistent with the messages we send in case of wrong type usage, replace + // the error with our exception, and keep the original one as inner one for troubleshooting + throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionIPAddressPreference), e); + } + } + + if (IsValidIPAddressPreference(eValue)) + { + return eValue; + } + else + { + throw ADP.InvalidEnumerationValue(typeof(SqlConnectionIPAddressPreference), (int)eValue); + } + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/PoolBlockingUtilities.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/PoolBlockingUtilities.cs new file mode 100644 index 0000000000..467ae48f97 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/ConnectionString/PoolBlockingUtilities.cs @@ -0,0 +1,136 @@ +// 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 Microsoft.Data.SqlClient; + +namespace Microsoft.Data.Common.ConnectionString +{ + internal static class PoolBlockingUtilities + { + internal static bool TryConvertToPoolBlockingPeriod(string value, out PoolBlockingPeriod result) + { + Debug.Assert(Enum.GetNames(typeof(PoolBlockingPeriod)).Length == 3, "PoolBlockingPeriod enum has changed, update needed"); + Debug.Assert(value != null, "TryConvertToPoolBlockingPeriod(null,...)"); + + if (StringComparer.OrdinalIgnoreCase.Equals(value, nameof(PoolBlockingPeriod.Auto))) + { + result = PoolBlockingPeriod.Auto; + return true; + } + else if (StringComparer.OrdinalIgnoreCase.Equals(value, nameof(PoolBlockingPeriod.AlwaysBlock))) + { + result = PoolBlockingPeriod.AlwaysBlock; + return true; + } + else if (StringComparer.OrdinalIgnoreCase.Equals(value, nameof(PoolBlockingPeriod.NeverBlock))) + { + result = PoolBlockingPeriod.NeverBlock; + return true; + } + else + { + result = DbConnectionStringDefaults.PoolBlockingPeriod; + return false; + } + } + + internal static bool IsValidPoolBlockingPeriodValue(PoolBlockingPeriod value) + { + Debug.Assert(Enum.GetNames(typeof(PoolBlockingPeriod)).Length == 3, "PoolBlockingPeriod enum has changed, update needed"); + return value == PoolBlockingPeriod.Auto || value == PoolBlockingPeriod.AlwaysBlock || value == PoolBlockingPeriod.NeverBlock; + } + + internal static string PoolBlockingPeriodToString(PoolBlockingPeriod value) + { + Debug.Assert(IsValidPoolBlockingPeriodValue(value)); + + return value switch + { + PoolBlockingPeriod.AlwaysBlock => nameof(PoolBlockingPeriod.AlwaysBlock), + PoolBlockingPeriod.NeverBlock => nameof(PoolBlockingPeriod.NeverBlock), + _ => nameof(PoolBlockingPeriod.Auto), + }; + } + + /// + /// This method attempts to convert the given value to a PoolBlockingPeriod enum. The algorithm is: + /// * if the value is from type string, it will be matched against PoolBlockingPeriod enum names only, using ordinal, case-insensitive comparer + /// * if the value is from type PoolBlockingPeriod, it will be used as is + /// * if the value is from integral type (SByte, Int16, Int32, Int64, Byte, UInt16, UInt32, or UInt64), it will be converted to enum + /// * if the value is another enum or any other type, it will be blocked with an appropriate ArgumentException + /// + /// in any case above, if the converted value is out of valid range, the method raises ArgumentOutOfRangeException. + /// + /// PoolBlockingPeriod value in the valid range + internal static PoolBlockingPeriod ConvertToPoolBlockingPeriod(string keyword, object value) + { + Debug.Assert(value != null, "ConvertToPoolBlockingPeriod(null)"); + if (value is string sValue) + { + // We could use Enum.TryParse here, but it accepts value combinations like + // "ReadOnly, ReadWrite" which are unwelcome here + // Also, Enum.TryParse is 100x slower than plain StringComparer.OrdinalIgnoreCase.Equals method. + + if (TryConvertToPoolBlockingPeriod(sValue, out PoolBlockingPeriod result)) + { + return result; + } + + // try again after remove leading & trailing whitespaces. + sValue = sValue.Trim(); + if (TryConvertToPoolBlockingPeriod(sValue, out result)) + { + return result; + } + + // string values must be valid + throw ADP.InvalidConnectionOptionValue(keyword); + } + else + { + // the value is not string, try other options + PoolBlockingPeriod eValue; + + if (value is PoolBlockingPeriod period) + { + // quick path for the most common case + eValue = period; + } + else if (value.GetType().IsEnum) + { + // explicitly block scenarios in which user tries to use wrong enum types, like: + // builder["PoolBlockingPeriod"] = EnvironmentVariableTarget.Process; + // workaround: explicitly cast non-PoolBlockingPeriod enums to int + throw ADP.ConvertFailed(value.GetType(), typeof(PoolBlockingPeriod), null); + } + else + { + try + { + // Enum.ToObject allows only integral and enum values (enums are blocked above), raising ArgumentException for the rest + eValue = (PoolBlockingPeriod)Enum.ToObject(typeof(PoolBlockingPeriod), value); + } + catch (ArgumentException e) + { + // to be consistent with the messages we send in case of wrong type usage, replace + // the error with our exception, and keep the original one as inner one for troubleshooting + throw ADP.ConvertFailed(value.GetType(), typeof(PoolBlockingPeriod), e); + } + } + + // ensure value is in valid range + if (IsValidPoolBlockingPeriodValue(eValue)) + { + return eValue; + } + else + { + throw ADP.InvalidEnumerationValue(typeof(PoolBlockingPeriod), (int)eValue); + } + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs deleted file mode 100644 index 61c189e738..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs +++ /dev/null @@ -1,1157 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using Microsoft.Data.SqlClient; - -namespace Microsoft.Data.Common -{ - internal static class DbConnectionStringBuilderUtil - { - internal static bool ConvertToBoolean(object value) - { - Debug.Assert(value != null, "ConvertToBoolean(null)"); - if (value is string svalue) - { - if (StringComparer.OrdinalIgnoreCase.Equals(svalue, "true") || StringComparer.OrdinalIgnoreCase.Equals(svalue, "yes")) - { - return true; - } - else if (StringComparer.OrdinalIgnoreCase.Equals(svalue, "false") || StringComparer.OrdinalIgnoreCase.Equals(svalue, "no")) - { - return false; - } - else - { - string tmp = svalue.Trim(); // Remove leading & trailing white space. - if (StringComparer.OrdinalIgnoreCase.Equals(tmp, "true") || StringComparer.OrdinalIgnoreCase.Equals(tmp, "yes")) - { - return true; - } - else if (StringComparer.OrdinalIgnoreCase.Equals(tmp, "false") || StringComparer.OrdinalIgnoreCase.Equals(tmp, "no")) - { - return false; - } - } - return bool.Parse(svalue); - } - try - { - return Convert.ToBoolean(value, CultureInfo.InvariantCulture); - } - catch (InvalidCastException e) - { - throw ADP.ConvertFailed(value.GetType(), typeof(bool), e); - } - } - - internal static bool ConvertToIntegratedSecurity(object value) - { - Debug.Assert(value != null, "ConvertToIntegratedSecurity(null)"); - if (value is string svalue) - { - if (StringComparer.OrdinalIgnoreCase.Equals(svalue, "sspi") || StringComparer.OrdinalIgnoreCase.Equals(svalue, "true") || StringComparer.OrdinalIgnoreCase.Equals(svalue, "yes")) - return true; - else if (StringComparer.OrdinalIgnoreCase.Equals(svalue, "false") || StringComparer.OrdinalIgnoreCase.Equals(svalue, "no")) - return false; - else - { - string tmp = svalue.Trim(); // Remove leading & trailing white space. - if (StringComparer.OrdinalIgnoreCase.Equals(tmp, "sspi") || StringComparer.OrdinalIgnoreCase.Equals(tmp, "true") || StringComparer.OrdinalIgnoreCase.Equals(tmp, "yes")) - return true; - else if (StringComparer.OrdinalIgnoreCase.Equals(tmp, "false") || StringComparer.OrdinalIgnoreCase.Equals(tmp, "no")) - return false; - } - return bool.Parse(svalue); - } - try - { - return Convert.ToBoolean(value, CultureInfo.InvariantCulture); - } - catch (InvalidCastException e) - { - throw ADP.ConvertFailed(value.GetType(), typeof(bool), e); - } - } - - internal static int ConvertToInt32(object value) - { - try - { - return Convert.ToInt32(value, CultureInfo.InvariantCulture); - } - catch (InvalidCastException e) - { - throw ADP.ConvertFailed(value.GetType(), typeof(int), e); - } - } - - internal static string ConvertToString(object value) - { - try - { - return Convert.ToString(value, CultureInfo.InvariantCulture); - } - catch (InvalidCastException e) - { - throw ADP.ConvertFailed(value.GetType(), typeof(string), e); - } - } - - #region <> - internal static bool TryConvertToPoolBlockingPeriod(string value, out PoolBlockingPeriod result) - { - Debug.Assert(Enum.GetNames(typeof(PoolBlockingPeriod)).Length == 3, "PoolBlockingPeriod enum has changed, update needed"); - Debug.Assert(value != null, "TryConvertToPoolBlockingPeriod(null,...)"); - - if (StringComparer.OrdinalIgnoreCase.Equals(value, nameof(PoolBlockingPeriod.Auto))) - { - result = PoolBlockingPeriod.Auto; - return true; - } - else if (StringComparer.OrdinalIgnoreCase.Equals(value, nameof(PoolBlockingPeriod.AlwaysBlock))) - { - result = PoolBlockingPeriod.AlwaysBlock; - return true; - } - else if (StringComparer.OrdinalIgnoreCase.Equals(value, nameof(PoolBlockingPeriod.NeverBlock))) - { - result = PoolBlockingPeriod.NeverBlock; - return true; - } - else - { - result = DbConnectionStringDefaults.PoolBlockingPeriod; - return false; - } - } - - internal static bool IsValidPoolBlockingPeriodValue(PoolBlockingPeriod value) - { - Debug.Assert(Enum.GetNames(typeof(PoolBlockingPeriod)).Length == 3, "PoolBlockingPeriod enum has changed, update needed"); - return value == PoolBlockingPeriod.Auto || value == PoolBlockingPeriod.AlwaysBlock || value == PoolBlockingPeriod.NeverBlock; - } - - internal static string PoolBlockingPeriodToString(PoolBlockingPeriod value) - { - Debug.Assert(IsValidPoolBlockingPeriodValue(value)); - - return value switch - { - PoolBlockingPeriod.AlwaysBlock => nameof(PoolBlockingPeriod.AlwaysBlock), - PoolBlockingPeriod.NeverBlock => nameof(PoolBlockingPeriod.NeverBlock), - _ => nameof(PoolBlockingPeriod.Auto), - }; - } - - /// - /// This method attempts to convert the given value to a PoolBlockingPeriod enum. The algorithm is: - /// * if the value is from type string, it will be matched against PoolBlockingPeriod enum names only, using ordinal, case-insensitive comparer - /// * if the value is from type PoolBlockingPeriod, it will be used as is - /// * if the value is from integral type (SByte, Int16, Int32, Int64, Byte, UInt16, UInt32, or UInt64), it will be converted to enum - /// * if the value is another enum or any other type, it will be blocked with an appropriate ArgumentException - /// - /// in any case above, if the converted value is out of valid range, the method raises ArgumentOutOfRangeException. - /// - /// PoolBlockingPeriod value in the valid range - internal static PoolBlockingPeriod ConvertToPoolBlockingPeriod(string keyword, object value) - { - Debug.Assert(value != null, "ConvertToPoolBlockingPeriod(null)"); - if (value is string sValue) - { - // We could use Enum.TryParse here, but it accepts value combinations like - // "ReadOnly, ReadWrite" which are unwelcome here - // Also, Enum.TryParse is 100x slower than plain StringComparer.OrdinalIgnoreCase.Equals method. - - if (TryConvertToPoolBlockingPeriod(sValue, out PoolBlockingPeriod result)) - { - return result; - } - - // try again after remove leading & trailing whitespaces. - sValue = sValue.Trim(); - if (TryConvertToPoolBlockingPeriod(sValue, out result)) - { - return result; - } - - // string values must be valid - throw ADP.InvalidConnectionOptionValue(keyword); - } - else - { - // the value is not string, try other options - PoolBlockingPeriod eValue; - - if (value is PoolBlockingPeriod period) - { - // quick path for the most common case - eValue = period; - } - else if (value.GetType().IsEnum) - { - // explicitly block scenarios in which user tries to use wrong enum types, like: - // builder["PoolBlockingPeriod"] = EnvironmentVariableTarget.Process; - // workaround: explicitly cast non-PoolBlockingPeriod enums to int - throw ADP.ConvertFailed(value.GetType(), typeof(PoolBlockingPeriod), null); - } - else - { - try - { - // Enum.ToObject allows only integral and enum values (enums are blocked above), raising ArgumentException for the rest - eValue = (PoolBlockingPeriod)Enum.ToObject(typeof(PoolBlockingPeriod), value); - } - catch (ArgumentException e) - { - // to be consistent with the messages we send in case of wrong type usage, replace - // the error with our exception, and keep the original one as inner one for troubleshooting - throw ADP.ConvertFailed(value.GetType(), typeof(PoolBlockingPeriod), e); - } - } - - // ensure value is in valid range - if (IsValidPoolBlockingPeriodValue(eValue)) - { - return eValue; - } - else - { - throw ADP.InvalidEnumerationValue(typeof(ApplicationIntent), (int)eValue); - } - } - } - #endregion - - internal static bool TryConvertToApplicationIntent(string value, out ApplicationIntent result) - { - Debug.Assert(Enum.GetNames(typeof(ApplicationIntent)).Length == 2, "ApplicationIntent enum has changed, update needed"); - Debug.Assert(value != null, "TryConvertToApplicationIntent(null,...)"); - - if (StringComparer.OrdinalIgnoreCase.Equals(value, nameof(ApplicationIntent.ReadOnly))) - { - result = ApplicationIntent.ReadOnly; - return true; - } - else if (StringComparer.OrdinalIgnoreCase.Equals(value, nameof(ApplicationIntent.ReadWrite))) - { - result = ApplicationIntent.ReadWrite; - return true; - } - else - { - result = DbConnectionStringDefaults.ApplicationIntent; - return false; - } - } - - internal static bool IsValidApplicationIntentValue(ApplicationIntent value) - { - Debug.Assert(Enum.GetNames(typeof(ApplicationIntent)).Length == 2, "ApplicationIntent enum has changed, update needed"); - return value == ApplicationIntent.ReadOnly || value == ApplicationIntent.ReadWrite; - } - - internal static string ApplicationIntentToString(ApplicationIntent value) - { - Debug.Assert(IsValidApplicationIntentValue(value)); - if (value == ApplicationIntent.ReadOnly) - { - return nameof(ApplicationIntent.ReadOnly); - } - else - { - return nameof(ApplicationIntent.ReadWrite); - } - } - - /// - /// This method attempts to convert the given value tp ApplicationIntent enum. The algorithm is: - /// * if the value is from type string, it will be matched against ApplicationIntent enum names only, using ordinal, case-insensitive comparer - /// * if the value is from type ApplicationIntent, it will be used as is - /// * if the value is from integral type (SByte, Int16, Int32, Int64, Byte, UInt16, UInt32, or UInt64), it will be converted to enum - /// * if the value is another enum or any other type, it will be blocked with an appropriate ArgumentException - /// - /// in any case above, if the converted value is out of valid range, the method raises ArgumentOutOfRangeException. - /// - /// application intent value in the valid range - internal static ApplicationIntent ConvertToApplicationIntent(string keyword, object value) - { - Debug.Assert(value != null, "ConvertToApplicationIntent(null)"); - if (value is string sValue) - { - // We could use Enum.TryParse here, but it accepts value combinations like - // "ReadOnly, ReadWrite" which are unwelcome here - // Also, Enum.TryParse is 100x slower than plain StringComparer.OrdinalIgnoreCase.Equals method. - - if (TryConvertToApplicationIntent(sValue, out ApplicationIntent result)) - { - return result; - } - - // try again after remove leading & trailing whitespaces. - sValue = sValue.Trim(); - if (TryConvertToApplicationIntent(sValue, out result)) - { - return result; - } - - // string values must be valid - throw ADP.InvalidConnectionOptionValue(keyword); - } - else - { - // the value is not string, try other options - ApplicationIntent eValue; - - if (value is ApplicationIntent intent) - { - // quick path for the most common case - eValue = intent; - } - else if (value.GetType().IsEnum) - { - // explicitly block scenarios in which user tries to use wrong enum types, like: - // builder["ApplicationIntent"] = EnvironmentVariableTarget.Process; - // workaround: explicitly cast non-ApplicationIntent enums to int - throw ADP.ConvertFailed(value.GetType(), typeof(ApplicationIntent), null); - } - else - { - try - { - // Enum.ToObject allows only integral and enum values (enums are blocked above), raising ArgumentException for the rest - eValue = (ApplicationIntent)Enum.ToObject(typeof(ApplicationIntent), value); - } - catch (ArgumentException e) - { - // to be consistent with the messages we send in case of wrong type usage, replace - // the error with our exception, and keep the original one as inner one for troubleshooting - throw ADP.ConvertFailed(value.GetType(), typeof(ApplicationIntent), e); - } - } - - // ensure value is in valid range - if (IsValidApplicationIntentValue(eValue)) - { - return eValue; - } - else - { - throw ADP.InvalidEnumerationValue(typeof(ApplicationIntent), (int)eValue); - } - } - } - - const string SqlPasswordString = "Sql Password"; - const string ActiveDirectoryPasswordString = "Active Directory Password"; - const string ActiveDirectoryIntegratedString = "Active Directory Integrated"; - const string ActiveDirectoryInteractiveString = "Active Directory Interactive"; - const string ActiveDirectoryServicePrincipalString = "Active Directory Service Principal"; - const string ActiveDirectoryDeviceCodeFlowString = "Active Directory Device Code Flow"; - internal const string ActiveDirectoryManagedIdentityString = "Active Directory Managed Identity"; - internal const string ActiveDirectoryMSIString = "Active Directory MSI"; - internal const string ActiveDirectoryDefaultString = "Active Directory Default"; - internal const string ActiveDirectoryWorkloadIdentityString = "Active Directory Workload Identity"; - -#if DEBUG - private static readonly string[] s_supportedAuthenticationModes = - { - "NotSpecified", - "SqlPassword", - "ActiveDirectoryPassword", - "ActiveDirectoryIntegrated", - "ActiveDirectoryInteractive", - "ActiveDirectoryServicePrincipal", - "ActiveDirectoryDeviceCodeFlow", - "ActiveDirectoryManagedIdentity", - "ActiveDirectoryMSI", - "ActiveDirectoryDefault", - "ActiveDirectoryWorkloadIdentity", - }; - - private static bool IsValidAuthenticationMethodEnum() - { - string[] names = Enum.GetNames(typeof(SqlAuthenticationMethod)); - int l = s_supportedAuthenticationModes.Length; - bool listValid; - if (listValid = names.Length == l) - { - for (int i = 0; i < l; i++) - { - if (string.Compare(s_supportedAuthenticationModes[i], names[i], StringComparison.Ordinal) != 0) - { - listValid = false; - } - } - } - return listValid; - } -#endif - - internal static bool TryConvertToAuthenticationType(string value, out SqlAuthenticationMethod result) - { -#if DEBUG - Debug.Assert(IsValidAuthenticationMethodEnum(), "SqlAuthenticationMethod enum has changed, update needed"); -#endif - bool isSuccess = false; - - if (StringComparer.InvariantCultureIgnoreCase.Equals(value, SqlPasswordString) - || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.SqlPassword, CultureInfo.InvariantCulture))) - { - result = SqlAuthenticationMethod.SqlPassword; - isSuccess = true; - } - else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryPasswordString) - || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryPassword, CultureInfo.InvariantCulture))) - { - result = SqlAuthenticationMethod.ActiveDirectoryPassword; - isSuccess = true; - } - else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryIntegratedString) - || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryIntegrated, CultureInfo.InvariantCulture))) - { - result = SqlAuthenticationMethod.ActiveDirectoryIntegrated; - isSuccess = true; - } - else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryInteractiveString) - || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryInteractive, CultureInfo.InvariantCulture))) - { - result = SqlAuthenticationMethod.ActiveDirectoryInteractive; - isSuccess = true; - } - else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryServicePrincipalString) - || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryServicePrincipal, CultureInfo.InvariantCulture))) - { - result = SqlAuthenticationMethod.ActiveDirectoryServicePrincipal; - isSuccess = true; - } - else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryDeviceCodeFlowString) - || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, CultureInfo.InvariantCulture))) - { - result = SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow; - isSuccess = true; - } - else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryManagedIdentityString) - || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryManagedIdentity, CultureInfo.InvariantCulture))) - { - result = SqlAuthenticationMethod.ActiveDirectoryManagedIdentity; - isSuccess = true; - } - else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryMSIString) - || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryMSI, CultureInfo.InvariantCulture))) - { - result = SqlAuthenticationMethod.ActiveDirectoryMSI; - isSuccess = true; - } - else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryDefaultString) - || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryDefault, CultureInfo.InvariantCulture))) - { - result = SqlAuthenticationMethod.ActiveDirectoryDefault; - isSuccess = true; - } - else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, ActiveDirectoryWorkloadIdentityString) - || StringComparer.InvariantCultureIgnoreCase.Equals(value, Convert.ToString(SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity, CultureInfo.InvariantCulture))) - { - result = SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity; - isSuccess = true; - } - else - { - result = DbConnectionStringDefaults.Authentication; - } - return isSuccess; - } - - /// - /// Convert a string value to the corresponding SqlConnectionColumnEncryptionSetting. - /// - /// - /// - /// - internal static bool TryConvertToColumnEncryptionSetting(string value, out SqlConnectionColumnEncryptionSetting result) - { - bool isSuccess = false; - - if (StringComparer.InvariantCultureIgnoreCase.Equals(value, nameof(SqlConnectionColumnEncryptionSetting.Enabled))) - { - result = SqlConnectionColumnEncryptionSetting.Enabled; - isSuccess = true; - } - else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, nameof(SqlConnectionColumnEncryptionSetting.Disabled))) - { - result = SqlConnectionColumnEncryptionSetting.Disabled; - isSuccess = true; - } - else - { - result = DbConnectionStringDefaults.ColumnEncryptionSetting; - } - - return isSuccess; - } - - /// - /// Is it a valid connection level column encryption setting ? - /// - /// - /// - internal static bool IsValidColumnEncryptionSetting(SqlConnectionColumnEncryptionSetting value) - { - Debug.Assert(Enum.GetNames(typeof(SqlConnectionColumnEncryptionSetting)).Length == 2, "SqlConnectionColumnEncryptionSetting enum has changed, update needed"); - return value == SqlConnectionColumnEncryptionSetting.Enabled || value == SqlConnectionColumnEncryptionSetting.Disabled; - } - - /// - /// Convert connection level column encryption setting value to string. - /// - /// - /// - internal static string ColumnEncryptionSettingToString(SqlConnectionColumnEncryptionSetting value) - { - Debug.Assert(IsValidColumnEncryptionSetting(value), "value is not a valid connection level column encryption setting."); - - return value switch - { - SqlConnectionColumnEncryptionSetting.Enabled => nameof(SqlConnectionColumnEncryptionSetting.Enabled), - SqlConnectionColumnEncryptionSetting.Disabled => nameof(SqlConnectionColumnEncryptionSetting.Disabled), - _ => null, - }; - } - - internal static bool IsValidAuthenticationTypeValue(SqlAuthenticationMethod value) - { - Debug.Assert(Enum.GetNames(typeof(SqlAuthenticationMethod)).Length == 11, "SqlAuthenticationMethod enum has changed, update needed"); - return value == SqlAuthenticationMethod.SqlPassword - || value == SqlAuthenticationMethod.ActiveDirectoryPassword - || value == SqlAuthenticationMethod.ActiveDirectoryIntegrated - || value == SqlAuthenticationMethod.ActiveDirectoryInteractive - || value == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal - || value == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow - || value == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity - || value == SqlAuthenticationMethod.ActiveDirectoryMSI - || value == SqlAuthenticationMethod.ActiveDirectoryDefault - || value == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity - || value == SqlAuthenticationMethod.NotSpecified; - } - - internal static string AuthenticationTypeToString(SqlAuthenticationMethod value) - { - Debug.Assert(IsValidAuthenticationTypeValue(value)); - - return value switch - { - SqlAuthenticationMethod.SqlPassword => SqlPasswordString, - SqlAuthenticationMethod.ActiveDirectoryPassword => ActiveDirectoryPasswordString, - SqlAuthenticationMethod.ActiveDirectoryIntegrated => ActiveDirectoryIntegratedString, - SqlAuthenticationMethod.ActiveDirectoryInteractive => ActiveDirectoryInteractiveString, - SqlAuthenticationMethod.ActiveDirectoryServicePrincipal => ActiveDirectoryServicePrincipalString, - SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow => ActiveDirectoryDeviceCodeFlowString, - SqlAuthenticationMethod.ActiveDirectoryManagedIdentity => ActiveDirectoryManagedIdentityString, - SqlAuthenticationMethod.ActiveDirectoryMSI => ActiveDirectoryMSIString, - SqlAuthenticationMethod.ActiveDirectoryDefault => ActiveDirectoryDefaultString, - SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity => ActiveDirectoryWorkloadIdentityString, - _ => null - }; - } - - internal static SqlAuthenticationMethod ConvertToAuthenticationType(string keyword, object value) - { - if (value == null) - { - return DbConnectionStringDefaults.Authentication; - } - - if (value is string sValue) - { - if (TryConvertToAuthenticationType(sValue, out SqlAuthenticationMethod result)) - { - return result; - } - - // try again after remove leading & trailing whitespaces. - sValue = sValue.Trim(); - if (TryConvertToAuthenticationType(sValue, out result)) - { - return result; - } - - // string values must be valid - throw ADP.InvalidConnectionOptionValue(keyword); - } - else - { - // the value is not string, try other options - SqlAuthenticationMethod eValue; - - if (value is SqlAuthenticationMethod method) - { - // quick path for the most common case - eValue = method; - } - else if (value.GetType().IsEnum) - { - // explicitly block scenarios in which user tries to use wrong enum types, like: - // builder["ApplicationIntent"] = EnvironmentVariableTarget.Process; - // workaround: explicitly cast non-ApplicationIntent enums to int - throw ADP.ConvertFailed(value.GetType(), typeof(SqlAuthenticationMethod), null); - } - else - { - try - { - // Enum.ToObject allows only integral and enum values (enums are blocked above), raising ArgumentException for the rest - eValue = (SqlAuthenticationMethod)Enum.ToObject(typeof(SqlAuthenticationMethod), value); - } - catch (ArgumentException e) - { - // to be consistent with the messages we send in case of wrong type usage, replace - // the error with our exception, and keep the original one as inner one for troubleshooting - throw ADP.ConvertFailed(value.GetType(), typeof(SqlAuthenticationMethod), e); - } - } - - // ensure value is in valid range - if (IsValidAuthenticationTypeValue(eValue)) - { - return eValue; - } - else - { - throw ADP.InvalidEnumerationValue(typeof(SqlAuthenticationMethod), (int)eValue); - } - } - } - - /// - /// Convert the provided value to a SqlConnectionColumnEncryptionSetting. - /// - /// - /// - /// - internal static SqlConnectionColumnEncryptionSetting ConvertToColumnEncryptionSetting(string keyword, object value) - { - if (value == null) - { - return DbConnectionStringDefaults.ColumnEncryptionSetting; - } - - if (value is string sValue) - { - if (TryConvertToColumnEncryptionSetting(sValue, out SqlConnectionColumnEncryptionSetting result)) - { - return result; - } - - // try again after remove leading & trailing whitespaces. - sValue = sValue.Trim(); - if (TryConvertToColumnEncryptionSetting(sValue, out result)) - { - return result; - } - - // string values must be valid - throw ADP.InvalidConnectionOptionValue(keyword); - } - else - { - // the value is not string, try other options - SqlConnectionColumnEncryptionSetting eValue; - - if (value is SqlConnectionColumnEncryptionSetting setting) - { - // quick path for the most common case - eValue = setting; - } - else if (value.GetType().IsEnum) - { - // explicitly block scenarios in which user tries to use wrong enum types, like: - // builder["SqlConnectionColumnEncryptionSetting"] = EnvironmentVariableTarget.Process; - // workaround: explicitly cast non-SqlConnectionColumnEncryptionSetting enums to int - throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionColumnEncryptionSetting), null); - } - else - { - try - { - // Enum.ToObject allows only integral and enum values (enums are blocked above), raising ArgumentException for the rest - eValue = (SqlConnectionColumnEncryptionSetting)Enum.ToObject(typeof(SqlConnectionColumnEncryptionSetting), value); - } - catch (ArgumentException e) - { - // to be consistent with the messages we send in case of wrong type usage, replace - // the error with our exception, and keep the original one as inner one for troubleshooting - throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionColumnEncryptionSetting), e); - } - } - - // ensure value is in valid range - if (IsValidColumnEncryptionSetting(eValue)) - { - return eValue; - } - else - { - throw ADP.InvalidEnumerationValue(typeof(SqlConnectionColumnEncryptionSetting), (int)eValue); - } - } - } - - #region <> - /// - /// Convert a string value to the corresponding SqlConnectionAttestationProtocol - /// - /// - /// - /// - internal static bool TryConvertToAttestationProtocol(string value, out SqlConnectionAttestationProtocol result) - { - if (StringComparer.InvariantCultureIgnoreCase.Equals(value, nameof(SqlConnectionAttestationProtocol.HGS))) - { - result = SqlConnectionAttestationProtocol.HGS; - return true; - } - else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, nameof(SqlConnectionAttestationProtocol.AAS))) - { - result = SqlConnectionAttestationProtocol.AAS; - return true; - } - else if (StringComparer.InvariantCultureIgnoreCase.Equals(value, nameof(SqlConnectionAttestationProtocol.None))) - { - result = SqlConnectionAttestationProtocol.None; - return true; - } - else - { - result = DbConnectionStringDefaults.AttestationProtocol; - return false; - } - } - - internal static bool IsValidAttestationProtocol(SqlConnectionAttestationProtocol value) - { - Debug.Assert(Enum.GetNames(typeof(SqlConnectionAttestationProtocol)).Length == 4, "SqlConnectionAttestationProtocol enum has changed, update needed"); - return value == SqlConnectionAttestationProtocol.NotSpecified - || value == SqlConnectionAttestationProtocol.HGS - || value == SqlConnectionAttestationProtocol.AAS - || value == SqlConnectionAttestationProtocol.None; - } - - internal static string AttestationProtocolToString(SqlConnectionAttestationProtocol value) - { - Debug.Assert(IsValidAttestationProtocol(value), "value is not a valid attestation protocol"); - - return value switch - { - SqlConnectionAttestationProtocol.AAS => nameof(SqlConnectionAttestationProtocol.AAS), - SqlConnectionAttestationProtocol.HGS => nameof(SqlConnectionAttestationProtocol.HGS), - SqlConnectionAttestationProtocol.None => nameof(SqlConnectionAttestationProtocol.None), - _ => null - }; - } - - internal static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(string keyword, object value) - { - if (value == null) - { - return DbConnectionStringDefaults.AttestationProtocol; - } - - if (value is string sValue) - { - // try again after remove leading & trailing whitespaces. - sValue = sValue.Trim(); - if (TryConvertToAttestationProtocol(sValue, out SqlConnectionAttestationProtocol result)) - { - return result; - } - - // string values must be valid - throw ADP.InvalidConnectionOptionValue(keyword); - } - else - { - // the value is not string, try other options - SqlConnectionAttestationProtocol eValue; - - if (value is SqlConnectionAttestationProtocol protocol) - { - eValue = protocol; - } - else if (value.GetType().IsEnum) - { - // explicitly block scenarios in which user tries to use wrong enum types, like: - // builder["SqlConnectionAttestationProtocol"] = EnvironmentVariableTarget.Process; - // workaround: explicitly cast non-SqlConnectionAttestationProtocol enums to int - throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionAttestationProtocol), null); - } - else - { - try - { - // Enum.ToObject allows only integral and enum values (enums are blocked above), raising ArgumentException for the rest - eValue = (SqlConnectionAttestationProtocol)Enum.ToObject(typeof(SqlConnectionAttestationProtocol), value); - } - catch (ArgumentException e) - { - // to be consistent with the messages we send in case of wrong type usage, replace - // the error with our exception, and keep the original one as inner one for troubleshooting - throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionAttestationProtocol), e); - } - } - - if (IsValidAttestationProtocol(eValue)) - { - return eValue; - } - else - { - throw ADP.InvalidEnumerationValue(typeof(SqlConnectionAttestationProtocol), (int)eValue); - } - } - } - - internal static SqlConnectionEncryptOption ConvertToSqlConnectionEncryptOption(string keyword, object value) - { - if (value is null) - { - return DbConnectionStringDefaults.Encrypt; - } - else if(value is SqlConnectionEncryptOption eValue) - { - return eValue; - } - else if (value is string sValue) - { - return SqlConnectionEncryptOption.Parse(sValue); - } - else if(value is bool bValue) - { - return SqlConnectionEncryptOption.Parse(bValue); - } - - throw ADP.InvalidConnectionOptionValue(keyword); - } - - #endregion - - #region <> - /// - /// IP Address Preference. - /// - private readonly static Dictionary s_preferenceNames = new(StringComparer.InvariantCultureIgnoreCase); - - static DbConnectionStringBuilderUtil() - { - foreach (SqlConnectionIPAddressPreference item in Enum.GetValues(typeof(SqlConnectionIPAddressPreference))) - { - s_preferenceNames.Add(item.ToString(), item); - } - } - - /// - /// Convert a string value to the corresponding IPAddressPreference. - /// - /// The string representation of the enumeration name to convert. - /// When this method returns, `result` contains an object of type `SqlConnectionIPAddressPreference` whose value is represented by `value` if the operation succeeds. - /// If the parse operation fails, `result` contains the default value of the `SqlConnectionIPAddressPreference` type. - /// `true` if the value parameter was converted successfully; otherwise, `false`. - internal static bool TryConvertToIPAddressPreference(string value, out SqlConnectionIPAddressPreference result) - { - if (!s_preferenceNames.TryGetValue(value, out result)) - { - result = DbConnectionStringDefaults.IPAddressPreference; - return false; - } - return true; - } - - /// - /// Verifies if the `value` is defined in the expected Enum. - /// - internal static bool IsValidIPAddressPreference(SqlConnectionIPAddressPreference value) - => value == SqlConnectionIPAddressPreference.IPv4First - || value == SqlConnectionIPAddressPreference.IPv6First - || value == SqlConnectionIPAddressPreference.UsePlatformDefault; - - internal static string IPAddressPreferenceToString(SqlConnectionIPAddressPreference value) - => Enum.GetName(typeof(SqlConnectionIPAddressPreference), value); - - internal static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value) - { - if (value is null) - { - return DbConnectionStringDefaults.IPAddressPreference; // IPv4First - } - - if (value is string sValue) - { - // try again after remove leading & trailing whitespaces. - sValue = sValue.Trim(); - if (TryConvertToIPAddressPreference(sValue, out SqlConnectionIPAddressPreference result)) - { - return result; - } - - // string values must be valid - throw ADP.InvalidConnectionOptionValue(keyword); - } - else - { - // the value is not string, try other options - SqlConnectionIPAddressPreference eValue; - - if (value is SqlConnectionIPAddressPreference preference) - { - eValue = preference; - } - else if (value.GetType().IsEnum) - { - // explicitly block scenarios in which user tries to use wrong enum types, like: - // builder["SqlConnectionIPAddressPreference"] = EnvironmentVariableTarget.Process; - // workaround: explicitly cast non-SqlConnectionIPAddressPreference enums to int - throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionIPAddressPreference), null); - } - else - { - try - { - // Enum.ToObject allows only integral and enum values (enums are blocked above), raising ArgumentException for the rest - eValue = (SqlConnectionIPAddressPreference)Enum.ToObject(typeof(SqlConnectionIPAddressPreference), value); - } - catch (ArgumentException e) - { - // to be consistent with the messages we send in case of wrong type usage, replace - // the error with our exception, and keep the original one as inner one for troubleshooting - throw ADP.ConvertFailed(value.GetType(), typeof(SqlConnectionIPAddressPreference), e); - } - } - - if (IsValidIPAddressPreference(eValue)) - { - return eValue; - } - else - { - throw ADP.InvalidEnumerationValue(typeof(SqlConnectionIPAddressPreference), (int)eValue); - } - } - } - #endregion - } - - internal static class DbConnectionStringDefaults - { - internal const ApplicationIntent ApplicationIntent = Microsoft.Data.SqlClient.ApplicationIntent.ReadWrite; - internal const string ApplicationName = -#if NETFRAMEWORK - "Framework Microsoft SqlClient Data Provider"; -#else - "Core Microsoft SqlClient Data Provider"; -#endif - internal const string AttachDBFilename = ""; - internal const int CommandTimeout = 30; - internal const int ConnectTimeout = 15; - internal const bool ContextConnection = false; - -#if NETFRAMEWORK - internal const bool ConnectionReset = true; - internal static readonly bool TransparentNetworkIPResolution = !LocalAppContextSwitches.DisableTNIRByDefault; - internal const string NetworkLibrary = ""; -#endif - internal const string CurrentLanguage = ""; - internal const string DataSource = ""; - internal static readonly SqlConnectionEncryptOption Encrypt = SqlConnectionEncryptOption.Mandatory; - internal const string HostNameInCertificate = ""; - internal const string ServerCertificate = ""; - internal const bool Enlist = true; - internal const string FailoverPartner = ""; - internal const string InitialCatalog = ""; - internal const bool IntegratedSecurity = false; - internal const int LoadBalanceTimeout = 0; // default of 0 means don't use - internal const bool MultipleActiveResultSets = false; - internal const bool MultiSubnetFailover = false; - internal const int MaxPoolSize = 100; - internal const int MinPoolSize = 0; - internal const int PacketSize = 8000; - internal const string Password = ""; - internal const bool PersistSecurityInfo = false; - internal const bool Pooling = true; - internal const bool TrustServerCertificate = false; - internal const string TypeSystemVersion = "Latest"; - internal const string UserID = ""; - internal const bool UserInstance = false; - internal const bool Replication = false; - internal const string WorkstationID = ""; - internal const string TransactionBinding = "Implicit Unbind"; - internal const int ConnectRetryCount = 1; - internal const int ConnectRetryInterval = 10; - internal static readonly SqlAuthenticationMethod Authentication = SqlAuthenticationMethod.NotSpecified; - internal const SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting = SqlConnectionColumnEncryptionSetting.Disabled; - internal const string EnclaveAttestationUrl = ""; - internal const SqlConnectionAttestationProtocol AttestationProtocol = SqlConnectionAttestationProtocol.NotSpecified; - internal const SqlConnectionIPAddressPreference IPAddressPreference = SqlConnectionIPAddressPreference.IPv4First; - internal const PoolBlockingPeriod PoolBlockingPeriod = SqlClient.PoolBlockingPeriod.Auto; - internal const string ServerSPN = ""; - internal const string FailoverPartnerSPN = ""; - } - - internal static class DbConnectionStringKeywords - { -#if NETFRAMEWORK - // Odbc - internal const string Driver = "Driver"; - internal const string Dsn = "Dsn"; - internal const string FileDsn = "FileDsn"; - internal const string SaveFile = "SaveFile"; - - // OleDb - internal const string FileName = "File Name"; - internal const string OleDbServices = "OLE DB Services"; - internal const string Provider = "Provider"; - - // OracleClient - internal const string Unicode = "Unicode"; - internal const string OmitOracleConnectionName = "Omit Oracle Connection Name"; -#endif - // SqlClient - internal const string ApplicationIntent = "Application Intent"; - internal const string ApplicationName = "Application Name"; - internal const string AttachDBFilename = "AttachDbFilename"; - internal const string ConnectTimeout = "Connect Timeout"; - internal const string CommandTimeout = "Command Timeout"; - internal const string ConnectionReset = "Connection Reset"; - internal const string ContextConnection = "Context Connection"; - internal const string CurrentLanguage = "Current Language"; - internal const string Encrypt = "Encrypt"; - internal const string HostNameInCertificate = "Host Name In Certificate"; - internal const string ServerCertificate = "Server Certificate"; - internal const string FailoverPartner = "Failover Partner"; - internal const string InitialCatalog = "Initial Catalog"; - internal const string MultipleActiveResultSets = "Multiple Active Result Sets"; - internal const string MultiSubnetFailover = "Multi Subnet Failover"; - internal const string NetworkLibrary = "Network Library"; - internal const string PacketSize = "Packet Size"; - internal const string Replication = "Replication"; - internal const string TransactionBinding = "Transaction Binding"; - internal const string TrustServerCertificate = "Trust Server Certificate"; - internal const string TypeSystemVersion = "Type System Version"; - internal const string UserInstance = "User Instance"; - internal const string WorkstationID = "Workstation ID"; - internal const string ConnectRetryCount = "Connect Retry Count"; - internal const string ConnectRetryInterval = "Connect Retry Interval"; - internal const string Authentication = "Authentication"; - internal const string ColumnEncryptionSetting = "Column Encryption Setting"; - internal const string EnclaveAttestationUrl = "Enclave Attestation Url"; - internal const string AttestationProtocol = "Attestation Protocol"; - internal const string IPAddressPreference = "IP Address Preference"; - internal const string ServerSPN = "Server SPN"; - internal const string FailoverPartnerSPN = "Failover Partner SPN"; - internal const string TransparentNetworkIPResolution = "Transparent Network IP Resolution"; - - // common keywords (OleDb, OracleClient, SqlClient) - internal const string DataSource = "Data Source"; - internal const string IntegratedSecurity = "Integrated Security"; - internal const string Password = "Password"; - internal const string PersistSecurityInfo = "Persist Security Info"; - internal const string UserID = "User ID"; - - // managed pooling (OracleClient, SqlClient) - internal const string Enlist = "Enlist"; - internal const string LoadBalanceTimeout = "Load Balance Timeout"; - internal const string MaxPoolSize = "Max Pool Size"; - internal const string Pooling = "Pooling"; - internal const string MinPoolSize = "Min Pool Size"; - internal const string PoolBlockingPeriod = "Pool Blocking Period"; - } - - internal static class DbConnectionStringSynonyms - { - //internal const string TransparentNetworkIPResolution = TRANSPARENTNETWORKIPRESOLUTION; - internal const string TRANSPARENTNETWORKIPRESOLUTION = "transparentnetworkipresolution"; - - //internal const string ApplicationName = APP; - internal const string APP = "app"; - - // internal const string IPAddressPreference = IPADDRESSPREFERENCE; - internal const string IPADDRESSPREFERENCE = "ipaddresspreference"; - - //internal const string ApplicationIntent = APPLICATIONINTENT; - internal const string APPLICATIONINTENT = "applicationintent"; - - //internal const string AttachDBFilename = EXTENDEDPROPERTIES+","+INITIALFILENAME; - internal const string EXTENDEDPROPERTIES = "extended properties"; - internal const string INITIALFILENAME = "initial file name"; - - // internal const string HostNameInCertificate = HOSTNAMEINCERTIFICATE; - internal const string HOSTNAMEINCERTIFICATE = "hostnameincertificate"; - - // internal const string ServerCertificate = SERVERCERTIFICATE; - internal const string SERVERCERTIFICATE = "servercertificate"; - - //internal const string ConnectTimeout = CONNECTIONTIMEOUT+","+TIMEOUT; - internal const string CONNECTIONTIMEOUT = "connection timeout"; - internal const string TIMEOUT = "timeout"; - - //internal const string ConnectRetryCount = CONNECTRETRYCOUNT; - internal const string CONNECTRETRYCOUNT = "connectretrycount"; - - //internal const string ConnectRetryInterval = CONNECTRETRYINTERVAL; - internal const string CONNECTRETRYINTERVAL = "connectretryinterval"; - - //internal const string CurrentLanguage = LANGUAGE; - internal const string LANGUAGE = "language"; - - //internal const string OraDataSource = SERVER; - //internal const string SqlDataSource = ADDR+","+ADDRESS+","+SERVER+","+NETWORKADDRESS; - internal const string ADDR = "addr"; - internal const string ADDRESS = "address"; - internal const string SERVER = "server"; - internal const string NETWORKADDRESS = "network address"; - - //internal const string InitialCatalog = DATABASE; - internal const string DATABASE = "database"; - - //internal const string IntegratedSecurity = TRUSTEDCONNECTION; - internal const string TRUSTEDCONNECTION = "trusted_connection"; // underscore introduced in everett - - //internal const string LoadBalanceTimeout = ConnectionLifetime; - internal const string ConnectionLifetime = "connection lifetime"; - - //internal const string MultipleActiveResultSets = MULTIPLEACTIVERESULTSETS; - internal const string MULTIPLEACTIVERESULTSETS = "multipleactiveresultsets"; - - //internal const string MultiSubnetFailover = MULTISUBNETFAILOVER; - internal const string MULTISUBNETFAILOVER = "multisubnetfailover"; - - //internal const string NetworkLibrary = NET+","+NETWORK; - internal const string NET = "net"; - internal const string NETWORK = "network"; - - //internal const string PoolBlockingPeriod = POOLBLOCKINGPERIOD; - internal const string POOLBLOCKINGPERIOD = "poolblockingperiod"; - - //internal const string Password = Pwd; - internal const string Pwd = "pwd"; - - //internal const string PersistSecurityInfo = PERSISTSECURITYINFO; - internal const string PERSISTSECURITYINFO = "persistsecurityinfo"; - - //internal const string TrustServerCertificate = TRUSTSERVERCERTIFICATE; - internal const string TRUSTSERVERCERTIFICATE = "trustservercertificate"; - - //internal const string UserID = UID+","+User; - internal const string UID = "uid"; - internal const string User = "user"; - - //internal const string WorkstationID = WSID; - internal const string WSID = "wsid"; - - //internal const string server SPNs - internal const string ServerSPN = "ServerSPN"; - internal const string FailoverPartnerSPN = "FailoverPartnerSPN"; - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs index b25f9a2d3b..d4ea183312 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionClosed.cs @@ -2,11 +2,13 @@ // 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 System.Data; using System.Data.Common; using System.Diagnostics; using System.Threading.Tasks; +using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.SqlClient.ConnectionPool; namespace Microsoft.Data.ProviderBase { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs index 0fdd85c4bf..399b98435b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionFactory.cs @@ -10,14 +10,16 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.SqlClient; +using Microsoft.Data.SqlClient.ConnectionPool; namespace Microsoft.Data.ProviderBase { internal abstract class DbConnectionFactory { private Dictionary _connectionPoolGroups; - private readonly List _poolsToRelease; + private readonly List _poolsToRelease; private readonly List _poolGroupsToRelease; private readonly Timer _pruningTimer; @@ -33,33 +35,13 @@ internal abstract class DbConnectionFactory private static Task[] s_pendingOpenNonPooled = new Task[Environment.ProcessorCount]; private static Task s_completedTask; -#if NETFRAMEWORK - private readonly DbConnectionPoolCounters _performanceCounters; - - protected DbConnectionFactory() : this(DbConnectionPoolCountersNoCounters.SingletonInstance) { } - - protected DbConnectionFactory(DbConnectionPoolCounters performanceCounters) - { - _performanceCounters = performanceCounters; - _connectionPoolGroups = new Dictionary(); - _poolsToRelease = new List(); - _poolGroupsToRelease = new List(); - _pruningTimer = CreatePruningTimer(); - } - - internal DbConnectionPoolCounters PerformanceCounters - { - get { return _performanceCounters; } - } -#else protected DbConnectionFactory() { _connectionPoolGroups = new Dictionary(); - _poolsToRelease = new List(); + _poolsToRelease = new List(); _poolGroupsToRelease = new List(); _pruningTimer = CreatePruningTimer(); } -#endif public abstract DbProviderFactory ProviderFactory { @@ -109,10 +91,8 @@ public void ClearPool(DbConnectionPoolKey key) } } - internal virtual DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(DbConnectionOptions connectionOptions) - { - return null; - } + internal abstract DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo( + DbConnectionOptions connectionOptions); protected virtual DbMetaDataFactory CreateMetaDataFactory(DbConnectionInternal internalConnection, out bool cacheMetaDataFactory) { @@ -134,19 +114,14 @@ internal DbConnectionInternal CreateNonPooledConnection(DbConnection owningConne DbConnectionInternal newConnection = CreateConnection(connectionOptions, poolKey, poolGroupProviderInfo, null, owningConnection, userOptions); if (newConnection != null) { -#if NETFRAMEWORK - PerformanceCounters.HardConnectsPerSecond.Increment(); - newConnection.MakeNonPooledObject(owningConnection, PerformanceCounters); -#else - SqlClientEventSource.Log.HardConnectRequest(); + SqlClientEventSource.Metrics.HardConnectRequest(); newConnection.MakeNonPooledObject(owningConnection); -#endif } SqlClientEventSource.Log.TryTraceEvent(" {0}, Non-pooled database connection created.", ObjectID); return newConnection; } - internal DbConnectionInternal CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions) + internal DbConnectionInternal CreatePooledConnection(IDbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions) { Debug.Assert(pool != null, "null pool?"); DbConnectionPoolGroupProviderInfo poolGroupProviderInfo = pool.PoolGroup.ProviderInfo; @@ -154,21 +129,16 @@ internal DbConnectionInternal CreatePooledConnection(DbConnectionPool pool, DbCo if (newConnection != null) { -#if NETFRAMEWORK - PerformanceCounters.HardConnectsPerSecond.Increment(); -#else - SqlClientEventSource.Log.HardConnectRequest(); -#endif + SqlClientEventSource.Metrics.HardConnectRequest(); + newConnection.MakePooledConnection(pool); } SqlClientEventSource.Log.TryTraceEvent(" {0}, Pooled database connection created.", ObjectID); return newConnection; } - internal virtual DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo(DbConnectionOptions connectionOptions) - { - return null; - } + internal abstract DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo( + DbConnectionOptions connectionOptions); private Timer CreatePruningTimer() => ADP.UnsafeCreateTimer( @@ -203,7 +173,7 @@ internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSour Debug.Assert(owningConnection != null, "null owningConnection?"); DbConnectionPoolGroup poolGroup; - DbConnectionPool connectionPool; + IDbConnectionPool connectionPool; connection = null; // Work around race condition with clearing the pool between GetConnectionPool obtaining pool @@ -288,11 +258,8 @@ internal bool TryGetConnection(DbConnection owningConnection, TaskCompletionSour } connection = CreateNonPooledConnection(owningConnection, poolGroup, userOptions); -#if NETFRAMEWORK - PerformanceCounters.NumberOfNonPooledConnections.Increment(); -#else - SqlClientEventSource.Log.EnterNonPooledConnection(); -#endif + + SqlClientEventSource.Metrics.EnterNonPooledConnection(); } else { @@ -396,16 +363,12 @@ private void TryGetConnectionCompletedContinuation(Task ta } else { -#if NETFRAMEWORK - PerformanceCounters.NumberOfNonPooledConnections.Increment(); -#else - SqlClientEventSource.Log.EnterNonPooledConnection(); -#endif + SqlClientEventSource.Metrics.EnterNonPooledConnection(); } } } - private DbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnectionPoolGroup connectionPoolGroup) + private IDbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnectionPoolGroup connectionPoolGroup) { // if poolgroup is disabled, it will be replaced with a new entry @@ -436,7 +399,7 @@ private DbConnectionPool GetConnectionPool(DbConnection owningObject, DbConnecti Debug.Assert(connectionPoolGroup != null, "null connectionPoolGroup?"); SetConnectionPoolGroup(owningObject, connectionPoolGroup); } - DbConnectionPool connectionPool = connectionPoolGroup.GetConnectionPool(this); + IDbConnectionPool connectionPool = connectionPoolGroup.GetConnectionPool(this); return connectionPool; } @@ -509,11 +472,8 @@ internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnectionPoolKey key, D // lock prevents race condition with PruneConnectionPoolGroups newConnectionPoolGroups.Add(key, newConnectionPoolGroup); -#if NETFRAMEWORK - PerformanceCounters.NumberOfActiveConnectionPoolGroups.Increment(); -#else - SqlClientEventSource.Log.EnterActiveConnectionPoolGroup(); -#endif + + SqlClientEventSource.Metrics.EnterActiveConnectionPoolGroup(); connectionPoolGroup = newConnectionPoolGroup; _connectionPoolGroups = newConnectionPoolGroups; } @@ -567,8 +527,8 @@ private void PruneConnectionPoolGroups(object state) { if (0 != _poolsToRelease.Count) { - DbConnectionPool[] poolsToRelease = _poolsToRelease.ToArray(); - foreach (DbConnectionPool pool in poolsToRelease) + IDbConnectionPool[] poolsToRelease = _poolsToRelease.ToArray(); + foreach (IDbConnectionPool pool in poolsToRelease) { if (pool != null) { @@ -577,12 +537,9 @@ private void PruneConnectionPoolGroups(object state) if (0 == pool.Count) { _poolsToRelease.Remove(pool); - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, ReleasePool={1}", ObjectID, pool.ObjectID); -#if NETFRAMEWORK - PerformanceCounters.NumberOfInactiveConnectionPools.Decrement(); -#else - SqlClientEventSource.Log.ExitInactiveConnectionPool(); -#endif + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, ReleasePool={1}", ObjectID, pool.Id); + + SqlClientEventSource.Metrics.ExitInactiveConnectionPool(); } } } @@ -607,11 +564,8 @@ private void PruneConnectionPoolGroups(object state) { _poolGroupsToRelease.Remove(poolGroup); SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, ReleasePoolGroup={1}", ObjectID, poolGroup.ObjectID); -#if NETFRAMEWORK - PerformanceCounters.NumberOfInactiveConnectionPoolGroups.Decrement(); -#else - SqlClientEventSource.Log.ExitInactiveConnectionPoolGroup(); -#endif + + SqlClientEventSource.Metrics.ExitInactiveConnectionPoolGroup(); } } } @@ -638,9 +592,6 @@ private void PruneConnectionPoolGroups(object state) if (entry.Value.Prune()) { // may add entries to _poolsToRelease -#if NETFRAMEWORK - PerformanceCounters.NumberOfActiveConnectionPoolGroups.Decrement(); -#endif QueuePoolGroupForRelease(entry.Value); } else @@ -653,7 +604,7 @@ private void PruneConnectionPoolGroups(object state) } } - internal void QueuePoolForRelease(DbConnectionPool pool, bool clearing) + internal void QueuePoolForRelease(IDbConnectionPool pool, bool clearing) { // Queue the pool up for release -- we'll clear it out and dispose // of it as the last part of the pruning timer callback so we don't @@ -673,12 +624,8 @@ internal void QueuePoolForRelease(DbConnectionPool pool, bool clearing) } _poolsToRelease.Add(pool); } -#if NETFRAMEWORK - PerformanceCounters.NumberOfInactiveConnectionPools.Increment(); -#else - SqlClientEventSource.Log.EnterInactiveConnectionPool(); - SqlClientEventSource.Log.ExitActiveConnectionPool(); -#endif + SqlClientEventSource.Metrics.EnterInactiveConnectionPool(); + SqlClientEventSource.Metrics.ExitActiveConnectionPool(); } internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup) @@ -690,20 +637,18 @@ internal void QueuePoolGroupForRelease(DbConnectionPoolGroup poolGroup) { _poolGroupsToRelease.Add(poolGroup); } -#if NETFRAMEWORK - PerformanceCounters.NumberOfInactiveConnectionPoolGroups.Increment(); -#else - SqlClientEventSource.Log.EnterInactiveConnectionPoolGroup(); - SqlClientEventSource.Log.ExitActiveConnectionPoolGroup(); -#endif - } - virtual protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) - { - return CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningConnection); + SqlClientEventSource.Metrics.EnterInactiveConnectionPoolGroup(); + SqlClientEventSource.Metrics.ExitActiveConnectionPoolGroup(); } - abstract protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection); + protected abstract DbConnectionInternal CreateConnection( + DbConnectionOptions options, + DbConnectionPoolKey poolKey, + DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, + IDbConnectionPool pool, + DbConnection owningConnection, + DbConnectionOptions userOptions); abstract protected DbConnectionOptions CreateConnectionOptions(string connectionString, DbConnectionOptions previous); 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 9744b8db9b..b4dfb4f214 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionInternal.cs @@ -10,7 +10,9 @@ using System.Threading.Tasks; using System.Transactions; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.SqlClient; +using Microsoft.Data.SqlClient.ConnectionPool; #if NETFRAMEWORK using System.Runtime.ConstrainedExecution; @@ -107,7 +109,7 @@ internal bool IsEmancipated { // NOTE: There are race conditions between PrePush, PostPop and this // property getter -- only use this while this object is locked; - // (DbConnectionPool.Clear and ReclaimEmancipatedObjects + // (IDbConnectionPool.Clear and ReclaimEmancipatedObjects // do this for us) // The functionality is as follows: @@ -156,7 +158,7 @@ internal bool IsInPool /// /// The pooler that the connection came from (Pooled connections only) /// - internal DbConnectionPool Pool { get; private set; } + internal IDbConnectionPool Pool { get; private set; } public abstract string ServerVersion { get; } @@ -323,10 +325,6 @@ protected internal DbConnection Owner get => _owningObject.TryGetTarget(out DbConnection connection) ? connection : null; } - #if NETFRAMEWORK - protected DbConnectionPoolCounters PerformanceCounters { get; private set; } - #endif - protected virtual bool ReadyToPrepareTransaction { get => true; @@ -367,11 +365,7 @@ internal void ActivateConnection(Transaction transaction) Activate(transaction); - #if NETFRAMEWORK - PerformanceCounters.NumberOfActiveConnections.Increment(); - #else - SqlClientEventSource.Log.EnterActiveConnection(); - #endif + SqlClientEventSource.Metrics.EnterActiveConnection(); } internal void AddWeakReference(object value, int tag) @@ -400,7 +394,7 @@ internal void CleanupConnectionOnTransactionCompletion(Transaction transaction) { DetachTransaction(transaction, false); - DbConnectionPool pool = Pool; + IDbConnectionPool pool = Pool; pool?.TransactionEnded(transaction, this); } @@ -461,7 +455,7 @@ internal virtual void CloseConnection(DbConnection owningObject, DbConnectionFac { PrepareForCloseConnection(); - DbConnectionPool connectionPool = Pool; + IDbConnectionPool connectionPool = Pool; // Detach from enlisted transactions that are no longer active on close DetachCurrentTransactionIfEnded(); @@ -471,10 +465,10 @@ internal virtual void CloseConnection(DbConnection owningObject, DbConnectionFac // into the pool. if (connectionPool is not null) { - // PutObject calls Deactivate for us... - connectionPool.PutObject(this, owningObject); + // ReturnInternalConnection calls Deactivate for us... + connectionPool.ReturnInternalConnection(this, owningObject); - // NOTE: Before we leave the PutObject call, another thread may have + // NOTE: Before we leave the ReturnInternalConnection call, another thread may have // already popped the connection from the pool, so don't expect to be // able to verify it. } @@ -484,11 +478,7 @@ internal virtual void CloseConnection(DbConnection owningObject, DbConnectionFac // and transactions may not get cleaned up... Deactivate(); - #if NETFRAMEWORK - PerformanceCounters.HardDisconnectsPerSecond.Increment(); - #else - SqlClientEventSource.Log.HardDisconnectRequest(); - #endif + SqlClientEventSource.Metrics.HardDisconnectRequest(); // To prevent an endless recursion, we need to clear the owning object // before we call dispose so that we can't get here a second time... @@ -503,16 +493,8 @@ internal virtual void CloseConnection(DbConnection owningObject, DbConnectionFac } else { - #if NETFRAMEWORK - PerformanceCounters.NumberOfNonPooledConnections.Decrement(); - if (this is not SqlInternalConnectionSmi) - { - Dispose(); - } - #else - SqlClientEventSource.Log.ExitNonPooledConnection(); + SqlClientEventSource.Metrics.ExitNonPooledConnection(); Dispose(); - #endif } } } @@ -542,15 +524,7 @@ internal void DeactivateConnection() Debug.Assert(activateCount == 0, "activated multiple times?"); #endif - #if NETFRAMEWORK - if (PerformanceCounters is not null) - { - // Pool.Clear will DestroyObject that will clean performanceCounters before going here - PerformanceCounters.NumberOfActiveConnections.Decrement(); - } - #else - SqlClientEventSource.Log.ExitActiveConnection(); - #endif + SqlClientEventSource.Metrics.ExitActiveConnection(); if (!IsConnectionDoomed && Pool.UseLoadBalancing) { @@ -585,7 +559,7 @@ internal virtual void DelegatedTransactionEnded() Deactivate(); // call it one more time just in case - DbConnectionPool pool = Pool; + IDbConnectionPool pool = Pool; if (pool == null) { @@ -610,11 +584,7 @@ internal virtual void DelegatedTransactionEnded() // once and for all, or the server will have fits about us // leaving connections open until the client-side GC kicks // in. - #if NETFRAMEWORK - PerformanceCounters.NumberOfNonPooledConnections.Decrement(); - #else - SqlClientEventSource.Log.ExitNonPooledConnection(); - #endif + SqlClientEventSource.Metrics.ExitNonPooledConnection(); Dispose(); } @@ -689,10 +659,6 @@ public virtual void Dispose() IsConnectionDoomed = true; _enlistedTransactionOriginal = null; // should not be disposed - #if NETFRAMEWORK - PerformanceCounters = null; - #endif - // Dispose of the _enlistedTransaction since it is a clone of the original reference. // VSDD 780271 - _enlistedTransaction can be changed by another thread (TX end event) Transaction enlistedTransaction = Interlocked.Exchange(ref _enlistedTransaction, null); @@ -722,16 +688,8 @@ public virtual void Dispose() /// /// Used by DbConnectionFactory to indicate that this object IS NOT part of a connection pool. /// - #if NETFRAMEWORK - internal void MakeNonPooledObject(DbConnection owningObject, DbConnectionPoolCounters performanceCounters) - #else internal void MakeNonPooledObject(DbConnection owningObject) - #endif { - #if NETFRAMEWORK - PerformanceCounters = performanceCounters; - #endif - Pool = null; _owningObject.SetTarget(owningObject); _pooledCount = -1; @@ -741,14 +699,10 @@ internal void MakeNonPooledObject(DbConnection owningObject) /// Used by DbConnectionFactory to indicate that this object IS part of a connection pool. /// /// - internal void MakePooledConnection(DbConnectionPool connectionPool) + internal void MakePooledConnection(IDbConnectionPool connectionPool) { _createTime = DateTime.UtcNow; Pool = connectionPool; - - #if NETFRAMEWORK - PerformanceCounters = connectionPool.PerformanceCounters; - #endif } internal void NotifyWeakReference(int message) => @@ -764,7 +718,7 @@ internal virtual void OpenConnection(DbConnection outerConnection, DbConnectionF internal void PostPop(DbConnection newOwner) { - // Called by DbConnectionPool right after it pulls this from its pool, we take this + // Called by IDbConnectionPool right after it pulls this from its pool, we take this // opportunity to ensure ownership and pool counts are legit. Debug.Assert(!IsEmancipated, "pooled object not in pool"); @@ -804,7 +758,7 @@ internal virtual void PrepareForReplaceConnection() internal void PrePush(object expectedOwner) { - // Called by DbConnectionPool when we're about to be put into it's pool, we take this + // Called by IDbConnectionPool when we're about to be put into it's pool, we take this // opportunity to ensure ownership and pool counts are legit. // IMPORTANT NOTE: You must have taken a lock on the object before you call this method @@ -848,11 +802,7 @@ internal void SetInStasis() IsTxRootWaitingForTxEnd = true; SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Non-Pooled Connection has Delegated Transaction, waiting to Dispose.", ObjectID); - #if NETFRAMEWORK - PerformanceCounters.NumberOfStasisConnections.Increment(); - #else - SqlClientEventSource.Log.EnterStasisConnection(); - #endif + SqlClientEventSource.Metrics.EnterStasisConnection(); } /// @@ -1004,11 +954,7 @@ private void TerminateStasis(bool returningToPool) : "Delegated Transaction has ended, connection is closed/leaked. Disposing."; SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, {1}", ObjectID, message); - #if NETFRAMEWORK - PerformanceCounters.NumberOfStasisConnections.Decrement(); - #else - SqlClientEventSource.Log.ExitStasisConnection(); - #endif + SqlClientEventSource.Metrics.ExitStasisConnection(); IsTxRootWaitingForTxEnd = false; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolCounters.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolCounters.netfx.cs deleted file mode 100644 index 04b9e37a61..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolCounters.netfx.cs +++ /dev/null @@ -1,361 +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 Interop.Windows.Kernel32; - -#if NETFRAMEWORK - -namespace Microsoft.Data.ProviderBase -{ - - using System; - using System.Diagnostics; - using System.Reflection; - using System.Runtime.ConstrainedExecution; - using System.Runtime.Versioning; - using System.Security.Permissions; - using Microsoft.Data.Common; - - internal abstract class DbConnectionPoolCounters - { - private static class CreationData - { - - static internal readonly CounterCreationData HardConnectsPerSecond = new CounterCreationData( - "HardConnectsPerSecond", - "The number of actual connections per second that are being made to servers", - PerformanceCounterType.RateOfCountsPerSecond32); - - static internal readonly CounterCreationData HardDisconnectsPerSecond = new CounterCreationData( - "HardDisconnectsPerSecond", - "The number of actual disconnects per second that are being made to servers", - PerformanceCounterType.RateOfCountsPerSecond32); - - static internal readonly CounterCreationData SoftConnectsPerSecond = new CounterCreationData( - "SoftConnectsPerSecond", - "The number of connections we get from the pool per second", - PerformanceCounterType.RateOfCountsPerSecond32); - - static internal readonly CounterCreationData SoftDisconnectsPerSecond = new CounterCreationData( - "SoftDisconnectsPerSecond", - "The number of connections we return to the pool per second", - PerformanceCounterType.RateOfCountsPerSecond32); - - static internal readonly CounterCreationData NumberOfNonPooledConnections = new CounterCreationData( - "NumberOfNonPooledConnections", - "The number of connections that are not using connection pooling", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfPooledConnections = new CounterCreationData( - "NumberOfPooledConnections", - "The number of connections that are managed by the connection pooler", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfActiveConnectionPoolGroups = new CounterCreationData( - "NumberOfActiveConnectionPoolGroups", - "The number of unique connection strings", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfInactiveConnectionPoolGroups = new CounterCreationData( - "NumberOfInactiveConnectionPoolGroups", - "The number of unique connection strings waiting for pruning", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfActiveConnectionPools = new CounterCreationData( - "NumberOfActiveConnectionPools", - "The number of connection pools", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfInactiveConnectionPools = new CounterCreationData( - "NumberOfInactiveConnectionPools", - "The number of connection pools", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfActiveConnections = new CounterCreationData( - "NumberOfActiveConnections", - "The number of connections currently in-use", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfFreeConnections = new CounterCreationData( - "NumberOfFreeConnections", - "The number of connections currently available for use", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfStasisConnections = new CounterCreationData( - "NumberOfStasisConnections", - "The number of connections currently waiting to be made ready for use", - PerformanceCounterType.NumberOfItems32); - - static internal readonly CounterCreationData NumberOfReclaimedConnections = new CounterCreationData( - "NumberOfReclaimedConnections", - "The number of connections we reclaim from GC'd external connections", - PerformanceCounterType.NumberOfItems32); - }; - - sealed internal class Counter - { - private PerformanceCounter _instance; - - internal Counter(string categoryName, string instanceName, string counterName, PerformanceCounterType counterType) - { - if (ADP.s_isPlatformNT5) - { - try - { - if (!ADP.IsEmpty(categoryName) && !ADP.IsEmpty(instanceName)) - { - PerformanceCounter instance = new PerformanceCounter(); - instance.CategoryName = categoryName; - instance.CounterName = counterName; - instance.InstanceName = instanceName; - instance.InstanceLifetime = PerformanceCounterInstanceLifetime.Process; - instance.ReadOnly = false; - instance.RawValue = 0; // make sure we start out at zero - _instance = instance; - } - } - catch (InvalidOperationException e) - { - ADP.TraceExceptionWithoutRethrow(e); - // TODO: generate Application EventLog entry about inability to find perf counter - } - } - } - - - internal void Decrement() - { - PerformanceCounter instance = _instance; - if (instance != null) - { - instance.Decrement(); - } - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] - internal void Dispose() - { // TODO: race condition, Dispose at the same time as Increment/Decrement - PerformanceCounter instance = _instance; - _instance = null; - if (instance != null) - { - instance.RemoveInstance(); - // should we be calling instance.Close? - // if we do will it exacerbate the Dispose vs. Decrement race condition - //instance.Close(); - } - } - - internal void Increment() - { - PerformanceCounter instance = _instance; - if (instance != null) - { - instance.Increment(); - } - } - }; - - const int CounterInstanceNameMaxLength = 127; - - internal readonly Counter HardConnectsPerSecond; - internal readonly Counter HardDisconnectsPerSecond; - internal readonly Counter SoftConnectsPerSecond; - internal readonly Counter SoftDisconnectsPerSecond; - internal readonly Counter NumberOfNonPooledConnections; - internal readonly Counter NumberOfPooledConnections; - internal readonly Counter NumberOfActiveConnectionPoolGroups; - internal readonly Counter NumberOfInactiveConnectionPoolGroups; - internal readonly Counter NumberOfActiveConnectionPools; - internal readonly Counter NumberOfInactiveConnectionPools; - internal readonly Counter NumberOfActiveConnections; - internal readonly Counter NumberOfFreeConnections; - internal readonly Counter NumberOfStasisConnections; - internal readonly Counter NumberOfReclaimedConnections; - - protected DbConnectionPoolCounters() : this(null, null) - { - } - - protected DbConnectionPoolCounters(string categoryName, string categoryHelp) - { - AppDomain.CurrentDomain.DomainUnload += new EventHandler(this.UnloadEventHandler); - AppDomain.CurrentDomain.ProcessExit += new EventHandler(this.ExitEventHandler); - AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(this.ExceptionEventHandler); - - string instanceName = null; - - if (!ADP.IsEmpty(categoryName)) - { - if (ADP.s_isPlatformNT5) - { - instanceName = GetInstanceName(); - } - } - - // level 0-3: hard connects/disconnects, plus basic pool/pool entry statistics - string basicCategoryName = categoryName; - HardConnectsPerSecond = new Counter(basicCategoryName, instanceName, CreationData.HardConnectsPerSecond.CounterName, CreationData.HardConnectsPerSecond.CounterType); - HardDisconnectsPerSecond = new Counter(basicCategoryName, instanceName, CreationData.HardDisconnectsPerSecond.CounterName, CreationData.HardDisconnectsPerSecond.CounterType); - NumberOfNonPooledConnections = new Counter(basicCategoryName, instanceName, CreationData.NumberOfNonPooledConnections.CounterName, CreationData.NumberOfNonPooledConnections.CounterType); - NumberOfPooledConnections = new Counter(basicCategoryName, instanceName, CreationData.NumberOfPooledConnections.CounterName, CreationData.NumberOfPooledConnections.CounterType); - NumberOfActiveConnectionPoolGroups = new Counter(basicCategoryName, instanceName, CreationData.NumberOfActiveConnectionPoolGroups.CounterName, CreationData.NumberOfActiveConnectionPoolGroups.CounterType); - NumberOfInactiveConnectionPoolGroups = new Counter(basicCategoryName, instanceName, CreationData.NumberOfInactiveConnectionPoolGroups.CounterName, CreationData.NumberOfInactiveConnectionPoolGroups.CounterType); - NumberOfActiveConnectionPools = new Counter(basicCategoryName, instanceName, CreationData.NumberOfActiveConnectionPools.CounterName, CreationData.NumberOfActiveConnectionPools.CounterType); - NumberOfInactiveConnectionPools = new Counter(basicCategoryName, instanceName, CreationData.NumberOfInactiveConnectionPools.CounterName, CreationData.NumberOfInactiveConnectionPools.CounterType); - NumberOfStasisConnections = new Counter(basicCategoryName, instanceName, CreationData.NumberOfStasisConnections.CounterName, CreationData.NumberOfStasisConnections.CounterType); - NumberOfReclaimedConnections = new Counter(basicCategoryName, instanceName, CreationData.NumberOfReclaimedConnections.CounterName, CreationData.NumberOfReclaimedConnections.CounterType); - - // level 4: expensive stuff - string verboseCategoryName = null; - if (!ADP.IsEmpty(categoryName)) - { - // don't load TraceSwitch if no categoryName so that Odbc/OleDb have a chance of not loading TraceSwitch - // which are also used by System.Diagnostics.PerformanceCounter.ctor & System.Transactions.get_Current - TraceSwitch perfCtrSwitch = new TraceSwitch("ConnectionPoolPerformanceCounterDetail", "level of detail to track with connection pool performance counters"); - if (TraceLevel.Verbose == perfCtrSwitch.Level) - { - verboseCategoryName = categoryName; - } - } - SoftConnectsPerSecond = new Counter(verboseCategoryName, instanceName, CreationData.SoftConnectsPerSecond.CounterName, CreationData.SoftConnectsPerSecond.CounterType); - SoftDisconnectsPerSecond = new Counter(verboseCategoryName, instanceName, CreationData.SoftDisconnectsPerSecond.CounterName, CreationData.SoftDisconnectsPerSecond.CounterType); - NumberOfActiveConnections = new Counter(verboseCategoryName, instanceName, CreationData.NumberOfActiveConnections.CounterName, CreationData.NumberOfActiveConnections.CounterType); - NumberOfFreeConnections = new Counter(verboseCategoryName, instanceName, CreationData.NumberOfFreeConnections.CounterName, CreationData.NumberOfFreeConnections.CounterType); - } - - [FileIOPermission(SecurityAction.Assert, Unrestricted = true)] - private string GetAssemblyName() - { - string result = null; - - // First try GetEntryAssembly name, then AppDomain.FriendlyName. - Assembly assembly = Assembly.GetEntryAssembly(); - - if (assembly != null) - { - AssemblyName name = assembly.GetName(); - if (name != null) - { - result = name.Name; // MDAC 73469 - } - } - return result; - } - - // SxS: this method uses GetCurrentProcessId to construct the instance name. - // TODO: VSDD 534795 - remove the Resource* attributes if you do not use GetCurrentProcessId after the fix - [ResourceExposure(ResourceScope.None)] - [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] - private string GetInstanceName() - { - string result = null; - - string instanceName = GetAssemblyName(); // instance perfcounter name - - if (ADP.IsEmpty(instanceName)) - { - AppDomain appDomain = AppDomain.CurrentDomain; - if (appDomain != null) - { - instanceName = appDomain.FriendlyName; - } - } - - // TODO: If you do not use GetCurrentProcessId after fixing VSDD 534795, please remove Resource* attributes from this method - int pid = Kernel32Safe.GetCurrentProcessId(); - - - // SQLBUDT #366157 -there are several characters which have special meaning - // to PERFMON. They recommend that we translate them as shown below, to - // prevent problems. - - result = String.Format((IFormatProvider)null, "{0}[{1}]", instanceName, pid); - result = result.Replace('(', '[').Replace(')', ']').Replace('#', '_').Replace('/', '_').Replace('\\', '_'); - - // SQLBUVSTS #94625 - counter instance name cannot be greater than 127 - if (result.Length > CounterInstanceNameMaxLength) - { - // Replacing the middle part with "[...]" - // For example: if path is c:\long_path\very_(Ax200)_long__path\perftest.exe and process ID is 1234 than the resulted instance name will be: - // c:\long_path\very_(AxM)[...](AxN)_long__path\perftest.exe[1234] - // while M and N are adjusted to make each part before and after the [...] = 61 (making the total = 61 + 5 + 61 = 127) - const string insertString = "[...]"; - int firstPartLength = (CounterInstanceNameMaxLength - insertString.Length) / 2; - int lastPartLength = CounterInstanceNameMaxLength - firstPartLength - insertString.Length; - result = string.Format((IFormatProvider)null, "{0}{1}{2}", - result.Substring(0, firstPartLength), - insertString, - result.Substring(result.Length - lastPartLength, lastPartLength)); - - Debug.Assert(result.Length == CounterInstanceNameMaxLength, - string.Format((IFormatProvider)null, "wrong calculation of the instance name: expected {0}, actual: {1}", CounterInstanceNameMaxLength, result.Length)); - } - - return result; - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] - public void Dispose() - { - // ExceptionEventHandler with IsTerminating may be called before - // the Connection Close is called or the variables are initialized - SafeDispose(HardConnectsPerSecond); - SafeDispose(HardDisconnectsPerSecond); - SafeDispose(SoftConnectsPerSecond); - SafeDispose(SoftDisconnectsPerSecond); - SafeDispose(NumberOfNonPooledConnections); - SafeDispose(NumberOfPooledConnections); - SafeDispose(NumberOfActiveConnectionPoolGroups); - SafeDispose(NumberOfInactiveConnectionPoolGroups); - SafeDispose(NumberOfActiveConnectionPools); - SafeDispose(NumberOfActiveConnections); - SafeDispose(NumberOfFreeConnections); - SafeDispose(NumberOfStasisConnections); - SafeDispose(NumberOfReclaimedConnections); - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] - private void SafeDispose(Counter counter) - { // WebData 103603 - if (counter != null) - { - counter.Dispose(); - } - } - - [PrePrepareMethod] - void ExceptionEventHandler(object sender, UnhandledExceptionEventArgs e) - { - if (e != null && e.IsTerminating) - { - Dispose(); - } - } - - [PrePrepareMethod] - void ExitEventHandler(object sender, EventArgs e) - { - Dispose(); - } - - [PrePrepareMethod] - void UnloadEventHandler(object sender, EventArgs e) - { - Dispose(); - } - } - - sealed internal class DbConnectionPoolCountersNoCounters : DbConnectionPoolCounters - { - - public static readonly DbConnectionPoolCountersNoCounters SingletonInstance = new DbConnectionPoolCountersNoCounters(); - - private DbConnectionPoolCountersNoCounters() : base() - { - } - } -} - -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs index 5ca28b5458..4eea91478c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbMetaDataFactory.cs @@ -26,8 +26,7 @@ internal class DbMetaDataFactory private const string PopulationStringKey = "PopulationString"; private const string MaximumVersionKey = "MaximumVersion"; private const string MinimumVersionKey = "MinimumVersion"; - private const string DataSourceProductVersionNormalizedKey = "DataSourceProductVersionNormalized"; - private const string DataSourceProductVersionKey = "DataSourceProductVersion"; + private const string RestrictionDefaultKey = "RestrictionDefault"; private const string RestrictionNumberKey = "RestrictionNumber"; private const string NumberOfRestrictionsKey = "NumberOfRestrictions"; private const string RestrictionNameKey = "RestrictionName"; @@ -44,10 +43,10 @@ public DbMetaDataFactory(Stream xmlStream, string serverVersion, string normaliz ADP.CheckArgumentNull(serverVersion, nameof(serverVersion)); ADP.CheckArgumentNull(normalizedServerVersion, nameof(normalizedServerVersion)); - LoadDataSetFromXml(xmlStream); - _serverVersionString = serverVersion; _normalizedServerVersion = normalizedServerVersion; + + LoadDataSetFromXml(xmlStream); } protected DataSet CollectionDataSet => _metaDataCollectionsDataSet; @@ -317,27 +316,13 @@ internal DataRow FindMetaDataCollectionRow(string collectionName) } - private void FixUpVersion(DataTable dataSourceInfoTable) + private void FixUpDataSourceInformationRow(DataRow dataSourceInfoRow) { - Debug.Assert(dataSourceInfoTable.TableName == DbMetaDataCollectionNames.DataSourceInformation); - DataColumn versionColumn = dataSourceInfoTable.Columns[DataSourceProductVersionKey]; - DataColumn normalizedVersionColumn = dataSourceInfoTable.Columns[DataSourceProductVersionNormalizedKey]; - - if ((versionColumn == null) || (normalizedVersionColumn == null)) - { - throw ADP.MissingDataSourceInformationColumn(); - } - - if (dataSourceInfoTable.Rows.Count != 1) - { - throw ADP.IncorrectNumberOfDataSourceInformationRows(); - } + Debug.Assert(dataSourceInfoRow.Table.Columns.Contains(DbMetaDataColumnNames.DataSourceProductVersion)); + Debug.Assert(dataSourceInfoRow.Table.Columns.Contains(DbMetaDataColumnNames.DataSourceProductVersionNormalized)); - DataRow dataSourceInfoRow = dataSourceInfoTable.Rows[0]; - - dataSourceInfoRow[versionColumn] = _serverVersionString; - dataSourceInfoRow[normalizedVersionColumn] = _normalizedServerVersion; - dataSourceInfoRow.AcceptChanges(); + dataSourceInfoRow[DbMetaDataColumnNames.DataSourceProductVersion] = _serverVersionString; + dataSourceInfoRow[DbMetaDataColumnNames.DataSourceProductVersionNormalized] = _normalizedServerVersion; } @@ -441,15 +426,6 @@ public virtual DataTable GetSchema(DbConnection connection, string collectionNam requestedSchema = CloneAndFilterCollection(exactCollectionName, hiddenColumns); - - // TODO: Consider an alternate method that doesn't involve special casing -- perhaps _prepareCollection - - // for the data source information table we need to fix up the version columns at run time - // since the version is determined at run time - if (exactCollectionName == DbMetaDataCollectionNames.DataSourceInformation) - { - FixUpVersion(requestedSchema); - } break; case SqlCommandKey: @@ -502,18 +478,229 @@ private bool IncludeThisColumn(DataColumn sourceColumn, string[] hiddenColumnNam private void LoadDataSetFromXml(Stream XmlStream) { - _metaDataCollectionsDataSet = new DataSet + DataSet metaDataCollectionsDataSet = new DataSet("NewDataSet") { Locale = CultureInfo.InvariantCulture }; XmlReaderSettings settings = new() { - XmlResolver = null + XmlResolver = null, + IgnoreComments = true, + IgnoreWhitespace = true }; using XmlReader reader = XmlReader.Create(XmlStream, settings); - _metaDataCollectionsDataSet.ReadXml(reader); + + // Build up the schema DataSet manually, then load data from XmlStream. The schema of the DataSet is defined at: + // * https://learn.microsoft.com/en-us/sql/connect/ado-net/common-schema-collections + // * https://learn.microsoft.com/en-us/sql/connect/ado-net/sql-server-schema-collections + // Building the schema manually is necessary because DataSet.ReadXml uses XML serialization. This is slow, and it + // increases the binary size of AOT assemblies by approximately 4MB. + + bool readContainer = reader.Read(); + + // Skip past the XML declaration and the outer container element. This XML document is hardcoded; + // these checks will need to be adjusted if its structure changes. + Debug.Assert(readContainer); + Debug.Assert(reader.NodeType == XmlNodeType.XmlDeclaration); + + readContainer = reader.Read(); + Debug.Assert(readContainer); + Debug.Assert(reader.NodeType == XmlNodeType.Element); + Debug.Assert(reader.Name == "MetaData"); + Debug.Assert(reader.Depth == 0); + + // Iterate over each "table element" of the outer container element. + // LoadDataTable will read the child elements of each table element. + while (reader.Read() + && reader.Depth == 1) + { + DataTable dataTable = null; + Action rowFixup = null; + + Debug.Assert(reader.NodeType == XmlNodeType.Element); + + switch (reader.Name) + { + case "MetaDataCollectionsTable": + dataTable = CreateMetaDataCollectionsDataTable(); + break; + case "RestrictionsTable": + dataTable = CreateRestrictionsDataTable(); + break; + case "DataSourceInformationTable": + dataTable = CreateDataSourceInformationDataTable(); + rowFixup = FixUpDataSourceInformationRow; + break; + case "DataTypesTable": + dataTable = CreateDataTypesDataTable(); + break; + case "ReservedWordsTable": + dataTable = CreateReservedWordsDataTable(); + break; + default: + Debug.Fail($"Unexpected table element name: {reader.Name}"); + break; + } + + if (dataTable != null) + { + LoadDataTable(reader, dataTable, rowFixup); + + metaDataCollectionsDataSet.Tables.Add(dataTable); + } + } + + _metaDataCollectionsDataSet = metaDataCollectionsDataSet; + } + + private static void LoadDataTable(XmlReader reader, DataTable table, Action rowFixup) + { + int parentDepth = reader.Depth; + + table.BeginLoadData(); + + // One outer loop per element, each loop reading every property of the row + while (reader.Read() + && reader.Depth == parentDepth + 1) + { + Debug.Assert(reader.NodeType == XmlNodeType.Element); + Debug.Assert(reader.Name == table.TableName); + + int childDepth = reader.Depth; + DataRow row = table.NewRow(); + + // Read every child property. Hardcoded structure - start with the element name, advance to the text, then to the EndElement + while (reader.Read() + && reader.Depth == childDepth + 1) + { + DataColumn column; + bool successfulRead; + + Debug.Assert(reader.NodeType == XmlNodeType.Element); + + column = table.Columns[reader.Name]; + Debug.Assert(column != null); + + successfulRead = reader.Read(); + Debug.Assert(successfulRead); + Debug.Assert(reader.NodeType == XmlNodeType.Text); + + row[column] = reader.Value; + + successfulRead = reader.Read(); + Debug.Assert(successfulRead); + Debug.Assert(reader.NodeType == XmlNodeType.EndElement); + } + + rowFixup?.Invoke(row); + + table.Rows.Add(row); + + Debug.Assert(reader.NodeType == XmlNodeType.EndElement); + } + + table.EndLoadData(); + table.AcceptChanges(); } + private static DataTable CreateMetaDataCollectionsDataTable() + => new DataTable(DbMetaDataCollectionNames.MetaDataCollections) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.CollectionName, typeof(string)), + new DataColumn(DbMetaDataColumnNames.NumberOfRestrictions, typeof(int)), + new DataColumn(DbMetaDataColumnNames.NumberOfIdentifierParts, typeof(int)), + new DataColumn(PopulationMechanismKey, typeof(string)), + new DataColumn(PopulationStringKey, typeof(string)), + new DataColumn(MinimumVersionKey, typeof(string)), + new DataColumn(MaximumVersionKey, typeof(string)) + } + }; + + private static DataTable CreateRestrictionsDataTable() + => new DataTable(DbMetaDataCollectionNames.Restrictions) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.CollectionName, typeof(string)), + new DataColumn(RestrictionNameKey, typeof(string)), + new DataColumn(ParameterNameKey, typeof(string)), + new DataColumn(RestrictionDefaultKey, typeof(string)), + new DataColumn(RestrictionNumberKey, typeof(int)), + new DataColumn(MinimumVersionKey, typeof(string)), + new DataColumn(MaximumVersionKey, typeof(string)) + } + }; + + private static DataTable CreateDataSourceInformationDataTable() + => new DataTable(DbMetaDataCollectionNames.DataSourceInformation) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.CompositeIdentifierSeparatorPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.DataSourceProductName, typeof(string)), + new DataColumn(DbMetaDataColumnNames.DataSourceProductVersion, typeof(string)), + new DataColumn(DbMetaDataColumnNames.DataSourceProductVersionNormalized, typeof(string)), + new DataColumn(DbMetaDataColumnNames.GroupByBehavior, typeof(GroupByBehavior)), + new DataColumn(DbMetaDataColumnNames.IdentifierPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.IdentifierCase, typeof(IdentifierCase)), + new DataColumn(DbMetaDataColumnNames.OrderByColumnsInSelect, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.ParameterMarkerFormat, typeof(string)), + new DataColumn(DbMetaDataColumnNames.ParameterMarkerPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.ParameterNameMaxLength, typeof(int)), + new DataColumn(DbMetaDataColumnNames.ParameterNamePattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.QuotedIdentifierPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.QuotedIdentifierCase, typeof(IdentifierCase)), + new DataColumn(DbMetaDataColumnNames.StatementSeparatorPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.StringLiteralPattern, typeof(string)), + new DataColumn(DbMetaDataColumnNames.SupportedJoinOperators, typeof(SupportedJoinOperators)) + } + }; + + private static DataTable CreateDataTypesDataTable() + => new DataTable(DbMetaDataCollectionNames.DataTypes) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.TypeName, typeof(string)), + new DataColumn(DbMetaDataColumnNames.ProviderDbType, typeof(int)), + new DataColumn(DbMetaDataColumnNames.ColumnSize, typeof(long)), + new DataColumn(DbMetaDataColumnNames.CreateFormat, typeof(string)), + new DataColumn(DbMetaDataColumnNames.CreateParameters, typeof(string)), + new DataColumn(DbMetaDataColumnNames.DataType, typeof(string)), + new DataColumn(DbMetaDataColumnNames.IsAutoIncrementable, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsBestMatch, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsCaseSensitive, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsFixedLength, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsFixedPrecisionScale, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsLong, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsNullable, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsSearchable, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsSearchableWithLike, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.IsUnsigned, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.MaximumScale, typeof(short)), + new DataColumn(DbMetaDataColumnNames.MinimumScale, typeof(short)), + new DataColumn(DbMetaDataColumnNames.IsConcurrencyType, typeof(bool)), + new DataColumn(MaximumVersionKey, typeof(string)), + new DataColumn(MinimumVersionKey, typeof(string)), + new DataColumn(DbMetaDataColumnNames.IsLiteralSupported, typeof(bool)), + new DataColumn(DbMetaDataColumnNames.LiteralPrefix, typeof(string)), + new DataColumn(DbMetaDataColumnNames.LiteralSuffix, typeof(string)) + } + }; + + private static DataTable CreateReservedWordsDataTable() + => new DataTable(DbMetaDataCollectionNames.ReservedWords) + { + Columns = + { + new DataColumn(DbMetaDataColumnNames.ReservedWord, typeof(string)), + new DataColumn(MinimumVersionKey, typeof(string)), + new DataColumn(MaximumVersionKey, typeof(string)) + } + }; + protected virtual DataTable PrepareCollection(string collectionName, string[] restrictions, DbConnection connection) { throw ADP.NotSupported(); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.netcore.Unix.cs similarity index 72% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.Unix.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.netcore.Unix.cs index 6ee3fe3329..196e38b579 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.Unix.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumerator.netcore.Unix.cs @@ -1,15 +1,20 @@ // 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. + +#if NET + using System.Data; using System.Data.Common; using Microsoft.Data.SqlClient.Server; namespace Microsoft.Data.Sql { - /// + /// public sealed partial class SqlDataSourceEnumerator : DbDataSourceEnumerator { private partial DataTable GetDataSourcesInternal() => SqlDataSourceEnumeratorManagedHelper.GetDataSources(); } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorManagedHelper.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorManagedHelper.netcore.cs similarity index 99% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorManagedHelper.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorManagedHelper.netcore.cs index 7bd2058419..8355d72d6f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorManagedHelper.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorManagedHelper.netcore.cs @@ -1,6 +1,9 @@ // 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. + +#if NET + using System.Collections.Generic; using System.Data; using Microsoft.Data.Sql; @@ -73,3 +76,5 @@ private static DataTable ParseServerEnumString(string serverInstances) } } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorNativeHelper.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorNativeHelper.cs index 65a7898c68..f53732940c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorNativeHelper.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Sql/SqlDataSourceEnumeratorNativeHelper.cs @@ -50,23 +50,23 @@ internal static DataTable GetDataSources() { } finally { - handle = SNINativeMethodWrapper.SNIServerEnumOpen(); + handle = SniNativeWrapper.SniServerEnumOpen(); SqlClientEventSource.Log.TryTraceEvent(" {2} returned handle = {3}.", nameof(SqlDataSourceEnumeratorNativeHelper), nameof(GetDataSources), - nameof(SNINativeMethodWrapper.SNIServerEnumOpen), handle); + nameof(SniNativeWrapper.SniServerEnumOpen), handle); } if (handle != ADP.s_ptrZero) { while (more && !TdsParserStaticMethods.TimeoutHasExpired(s_timeoutTime)) { - readLength = SNINativeMethodWrapper.SNIServerEnumRead(handle, buffer, bufferSize, out more); + readLength = SniNativeWrapper.SniServerEnumRead(handle, buffer, bufferSize, out more); SqlClientEventSource.Log.TryTraceEvent(" {2} returned 'readlength':{3}, and 'more':{4} with 'bufferSize' of {5}", nameof(SqlDataSourceEnumeratorNativeHelper), nameof(GetDataSources), - nameof(SNINativeMethodWrapper.SNIServerEnumRead), + nameof(SniNativeWrapper.SniServerEnumRead), readLength, more, bufferSize); if (readLength > bufferSize) { @@ -84,21 +84,21 @@ internal static DataTable GetDataSources() { if (handle != ADP.s_ptrZero) { - SNINativeMethodWrapper.SNIServerEnumClose(handle); + SniNativeWrapper.SniServerEnumClose(handle); SqlClientEventSource.Log.TryTraceEvent(" {2} called.", nameof(SqlDataSourceEnumeratorNativeHelper), nameof(GetDataSources), - nameof(SNINativeMethodWrapper.SNIServerEnumClose)); + nameof(SniNativeWrapper.SniServerEnumClose)); } } if (failure) { - Debug.Assert(false, $"{nameof(GetDataSources)}:{nameof(SNINativeMethodWrapper.SNIServerEnumRead)} returned bad length"); + Debug.Assert(false, $"{nameof(GetDataSources)}:{nameof(SniNativeWrapper.SniServerEnumRead)} returned bad length"); SqlClientEventSource.Log.TryTraceEvent(" {2} returned bad length, requested buffer {3}, received {4}", nameof(SqlDataSourceEnumeratorNativeHelper), nameof(GetDataSources), - nameof(SNINativeMethodWrapper.SNIServerEnumRead), + nameof(SniNativeWrapper.SniServerEnumRead), bufferSize, readLength); throw ADP.ArgumentOutOfRange(StringsHelper.GetString(Strings.ADP_ParameterValueOutOfRange, readLength), nameof(readLength)); @@ -158,7 +158,7 @@ private static DataTable ParseServerEnumString(string serverInstances) string query = "ServerName='" + serverName + "'"; - if (!ADP.IsEmpty(instanceName)) + if (!string.IsNullOrEmpty(instanceName)) { // SQL BU DT 20006584: only append instanceName if present. query += " AND InstanceName='" + instanceName + "'"; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs index a4982981f2..9cbec9516d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ActiveDirectoryAuthenticationProvider.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Azure.Core; using Azure.Identity; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Extensions.Caching.Memory; using Microsoft.Identity.Client; using Microsoft.Identity.Client.Extensibility; @@ -222,7 +223,11 @@ public override async Task AcquireTokenAsync(SqlAuthenti { if (!string.IsNullOrEmpty(parameters.UserId)) { + // The AcquireTokenByIntegratedWindowsAuth method is marked as obsolete in MSAL.NET + // but it is still a supported way to acquire tokens for Active Directory Integrated authentication. +#pragma warning disable CS0618 // Type or member is obsolete result = await app.AcquireTokenByIntegratedWindowsAuth(scopes) +#pragma warning restore CS0618 // Type or member is obsolete .WithCorrelationId(parameters.ConnectionId) .WithUsername(parameters.UserId) .ExecuteAsync(cancellationToken: cts.Token) @@ -230,7 +235,9 @@ public override async Task AcquireTokenAsync(SqlAuthenti } else { +#pragma warning disable CS0618 // Type or member is obsolete result = await app.AcquireTokenByIntegratedWindowsAuth(scopes) +#pragma warning restore CS0618 // Type or member is obsolete .WithCorrelationId(parameters.ConnectionId) .ExecuteAsync(cancellationToken: cts.Token) .ConfigureAwait(false); @@ -542,31 +549,24 @@ private static bool AreEqual(byte[] a1, byte[] a2) private IPublicClientApplication CreateClientAppInstance(PublicClientAppKey publicClientAppKey) { - IPublicClientApplication publicClientApplication; - -#if NETFRAMEWORK - if (_iWin32WindowFunc != null) - { - publicClientApplication = PublicClientApplicationBuilder.Create(publicClientAppKey._applicationClientId) - .WithAuthority(publicClientAppKey._authority) - .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) - .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) - .WithRedirectUri(publicClientAppKey._redirectUri) - .WithParentActivityOrWindow(_iWin32WindowFunc) - .Build(); - } - else -#endif + PublicClientApplicationBuilder builder = PublicClientApplicationBuilder + .CreateWithApplicationOptions(new PublicClientApplicationOptions + { + ClientId = publicClientAppKey._applicationClientId, + ClientName = DbConnectionStringDefaults.ApplicationName, + ClientVersion = Common.ADP.GetAssemblyVersion().ToString(), + RedirectUri = publicClientAppKey._redirectUri, + }) + .WithAuthority(publicClientAppKey._authority); + + #if NETFRAMEWORK + if (_iWin32WindowFunc is not null) { - publicClientApplication = PublicClientApplicationBuilder.Create(publicClientAppKey._applicationClientId) - .WithAuthority(publicClientAppKey._authority) - .WithClientName(Common.DbConnectionStringDefaults.ApplicationName) - .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString()) - .WithRedirectUri(publicClientAppKey._redirectUri) - .Build(); + builder.WithParentActivityOrWindow(_iWin32WindowFunc); } + #endif - return publicClientApplication; + return builder.Build(); } private static TokenCredentialData CreateTokenCredentialInstance(TokenCredentialKey tokenCredentialKey, string secret) @@ -588,7 +588,28 @@ private static TokenCredentialData CreateTokenCredentialInstance(TokenCredential defaultAzureCredentialOptions.WorkloadIdentityClientId = tokenCredentialKey._clientId; } - return new TokenCredentialData(new DefaultAzureCredential(defaultAzureCredentialOptions), GetHash(secret)); + // SqlClient is a library and provides support to acquire access + // token using 'DefaultAzureCredential' on user demand when they + // specify 'Authentication = Active Directory Default' in + // connection string. + // + // Default Azure Credential is instantiated by the calling + // application when using "Active Directory Default" + // authentication code to connect to Azure SQL instance. + // SqlClient is a library, doesn't instantiate the credential + // without running application instructions. + // + // Note that CodeQL suppression support can only detect + // suppression comments that appear immediately above the + // flagged statement, or appended to the end of the statement. + // Multi-line justifications are not supported. + // + // https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/codeql/codeql-semmle#guidance-on-suppressions + // + // CodeQL [SM05137] See above for justification. + DefaultAzureCredential cred = new(defaultAzureCredentialOptions); + + return new TokenCredentialData(cred, GetHash(secret)); } TokenCredentialOptions tokenCredentialOptions = new() { AuthorityHost = new Uri(tokenCredentialKey._authority) }; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs index 6aea3e88b8..a8aacf3a93 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/AzureAttestationBasedEnclaveProvider.cs @@ -401,7 +401,7 @@ private bool VerifyTokenSignature(string attestationToken, string tokenIssuerUrl RequireExpirationTime = true, ValidateLifetime = true, ValidateIssuer = true, - ValidateAudience = false, + ValidateAudience = false, // CodeQL [SM04387] Required for an external standard: Microsoft Azure Attestation does not support the audience claim. RequireSignedTokens = true, ValidIssuers = GenerateListOfIssuers(tokenIssuerUrl), IssuerSigningKeys = issuerSigningKeys diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs new file mode 100644 index 0000000000..c4772bb736 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPool.cs @@ -0,0 +1,120 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +using System; +using System.Collections.Concurrent; +using System.Data.Common; +using System.Threading.Tasks; +using System.Transactions; +using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.ProviderBase; + +#nullable enable + +namespace Microsoft.Data.SqlClient.ConnectionPool +{ + /// + /// A connection pool implementation based on the channel data structure. + /// Provides methods to manage the pool of connections, including acquiring and releasing connections. + /// + internal sealed class ChannelDbConnectionPool : IDbConnectionPool + { + #region Properties + /// + public ConcurrentDictionary AuthenticationContexts => throw new NotImplementedException(); + + /// + public DbConnectionFactory ConnectionFactory => throw new NotImplementedException(); + + /// + public int Count => throw new NotImplementedException(); + + /// + public bool ErrorOccurred => throw new NotImplementedException(); + + /// + public int Id => throw new NotImplementedException(); + + /// + public DbConnectionPoolIdentity Identity => throw new NotImplementedException(); + + /// + public bool IsRunning => throw new NotImplementedException(); + + /// + public TimeSpan LoadBalanceTimeout => throw new NotImplementedException(); + + /// + public DbConnectionPoolGroup PoolGroup => throw new NotImplementedException(); + + /// + public DbConnectionPoolGroupOptions PoolGroupOptions => throw new NotImplementedException(); + + /// + public DbConnectionPoolProviderInfo ProviderInfo => throw new NotImplementedException(); + + /// + public DbConnectionPoolState State + { + get => throw new NotImplementedException(); + set => throw new NotImplementedException(); + } + + /// + public bool UseLoadBalancing => throw new NotImplementedException(); + #endregion + + + + #region Methods + /// + public void Clear() + { + throw new NotImplementedException(); + } + + /// + public void PutObjectFromTransactedPool(DbConnectionInternal obj) + { + throw new NotImplementedException(); + } + + /// + public DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) + { + throw new NotImplementedException(); + } + + /// + public void ReturnInternalConnection(DbConnectionInternal obj, object owningObject) + { + throw new NotImplementedException(); + } + + /// + public void Shutdown() + { + throw new NotImplementedException(); + } + + /// + public void Startup() + { + throw new NotImplementedException(); + } + + /// + public void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject) + { + throw new NotImplementedException(); + } + + /// + public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal connection) + { + throw new NotImplementedException(); + } + #endregion + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolAuthenticationContext.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolAuthenticationContext.cs similarity index 98% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolAuthenticationContext.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolAuthenticationContext.cs index 29ac7fee31..71dbc3c714 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolAuthenticationContext.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolAuthenticationContext.cs @@ -7,7 +7,7 @@ using System.Runtime.ConstrainedExecution; using System.Threading; -namespace Microsoft.Data.ProviderBase +namespace Microsoft.Data.SqlClient.ConnectionPool { /// /// Represents the context of an authentication attempt when using the new active directory based authentication mechanisms. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolAuthenticationContextKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolAuthenticationContextKey.cs similarity index 98% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolAuthenticationContextKey.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolAuthenticationContextKey.cs index a6f15ca999..e12648b3d0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolAuthenticationContextKey.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolAuthenticationContextKey.cs @@ -5,7 +5,7 @@ using System; using System.Diagnostics; -namespace Microsoft.Data.ProviderBase +namespace Microsoft.Data.SqlClient.ConnectionPool { /// /// Represents the key of dbConnectionPoolAuthenticationContext. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolGroup.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs similarity index 88% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolGroup.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs index 687ee095f3..805beed2b5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolGroup.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroup.cs @@ -2,15 +2,16 @@ // 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 Microsoft.Data.SqlClient; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Threading; +using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.ProviderBase; -namespace Microsoft.Data.ProviderBase +namespace Microsoft.Data.SqlClient.ConnectionPool { // set_ConnectionString calls DbConnectionFactory.GetConnectionPoolGroup // when not found a new pool entry is created and potentially added @@ -34,7 +35,7 @@ sealed internal class DbConnectionPoolGroup private readonly DbConnectionOptions _connectionOptions; private readonly DbConnectionPoolKey _poolKey; private readonly DbConnectionPoolGroupOptions _poolGroupOptions; - private ConcurrentDictionary _poolCollection; + private ConcurrentDictionary _poolCollection; private int _state; // see PoolGroupState* below @@ -64,7 +65,7 @@ internal DbConnectionPoolGroup(DbConnectionOptions connectionOptions, DbConnecti // HybridDictionary does not create any sub-objects until add // so it is safe to use for non-pooled connection as long as // we check _poolGroupOptions first - _poolCollection = new ConcurrentDictionary(); + _poolCollection = new ConcurrentDictionary(); _state = PoolGroupStateActive; } @@ -113,28 +114,26 @@ internal int Clear() // will return the number of connections in the group after clearing has finished // First, note the old collection and create a new collection to be used - ConcurrentDictionary oldPoolCollection = null; + ConcurrentDictionary oldPoolCollection = null; lock (this) { if (_poolCollection.Count > 0) { oldPoolCollection = _poolCollection; - _poolCollection = new ConcurrentDictionary(); + _poolCollection = new ConcurrentDictionary(); } } // Then, if a new collection was created, release the pools from the old collection if (oldPoolCollection != null) { - foreach (KeyValuePair entry in oldPoolCollection) + foreach (KeyValuePair entry in oldPoolCollection) { - DbConnectionPool pool = entry.Value; + IDbConnectionPool pool = entry.Value; if (pool != null) { DbConnectionFactory connectionFactory = pool.ConnectionFactory; -#if NETFRAMEWORK - connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Decrement(); -#endif + connectionFactory.QueuePoolForRelease(pool, true); } } @@ -144,7 +143,7 @@ internal int Clear() return _poolCollection.Count; } - internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactory) + internal IDbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactory) { // When this method returns null it indicates that the connection // factory should not use pooling. @@ -152,7 +151,7 @@ internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactor // We don't support connection pooling on Win9x; // PoolGroupOptions will only be null when we're not supposed to pool // connections. - DbConnectionPool pool = null; + IDbConnectionPool pool = null; if (_poolGroupOptions != null) { #if NETFRAMEWORK @@ -187,7 +186,17 @@ internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactor if (!_poolCollection.TryGetValue(currentIdentity, out pool)) { DbConnectionPoolProviderInfo connectionPoolProviderInfo = connectionFactory.CreateConnectionPoolProviderInfo(ConnectionOptions); - DbConnectionPool newPool = new(connectionFactory, this, currentIdentity, connectionPoolProviderInfo); + + IDbConnectionPool newPool; + if (LocalAppContextSwitches.UseConnectionPoolV2) + { + throw new NotImplementedException(); + } + else + { + // WaitHandleDbConnectionPool is the v1 pool implementation, and used by default if UseConnectionPoolV2 is off + newPool = new WaitHandleDbConnectionPool(connectionFactory, this, currentIdentity, connectionPoolProviderInfo); + } if (MarkPoolGroupAsActive()) { @@ -197,10 +206,8 @@ internal DbConnectionPool GetConnectionPool(DbConnectionFactory connectionFactor newPool.Startup(); // must start pool before usage bool addResult = _poolCollection.TryAdd(currentIdentity, newPool); Debug.Assert(addResult, "No other pool with current identity should exist at this point"); - SqlClientEventSource.Log.EnterActiveConnectionPool(); -#if NETFRAMEWORK - connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Increment(); -#endif + SqlClientEventSource.Metrics.EnterActiveConnectionPool(); + pool = newPool; } else @@ -257,11 +264,11 @@ internal bool Prune() { if (_poolCollection.Count > 0) { - var newPoolCollection = new ConcurrentDictionary(); + var newPoolCollection = new ConcurrentDictionary(); - foreach (KeyValuePair entry in _poolCollection) + foreach (KeyValuePair entry in _poolCollection) { - DbConnectionPool pool = entry.Value; + IDbConnectionPool pool = entry.Value; if (pool != null) { // Actually prune the pool if there are no connections in the pool and no errors occurred. @@ -276,9 +283,7 @@ internal bool Prune() // pool into a list of pools to be released when they // are completely empty. DbConnectionFactory connectionFactory = pool.ConnectionFactory; -#if NETFRAMEWORK - connectionFactory.PerformanceCounters.NumberOfActiveConnectionPools.Decrement(); -#endif + connectionFactory.QueuePoolForRelease(pool, false); } else diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolGroupProviderInfo.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroupProviderInfo.cs similarity index 91% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolGroupProviderInfo.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroupProviderInfo.cs index 3eceb6d3e3..9ee83357bd 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolGroupProviderInfo.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolGroupProviderInfo.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace Microsoft.Data.ProviderBase +namespace Microsoft.Data.SqlClient.ConnectionPool { internal class DbConnectionPoolGroupProviderInfo { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolIdentity.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.Unix.cs similarity index 88% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolIdentity.Unix.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.Unix.cs index 95769c3e6c..3eadc8761a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolIdentity.Unix.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.Unix.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace Microsoft.Data.ProviderBase +namespace Microsoft.Data.SqlClient.ConnectionPool { partial class DbConnectionPoolIdentity { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolIdentity.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.Windows.cs similarity index 85% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolIdentity.Windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.Windows.cs index 62fbc34aed..99783ff3c7 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolIdentity.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.Windows.cs @@ -8,7 +8,7 @@ using System.Security.Principal; using Microsoft.Data.SqlClient; -namespace Microsoft.Data.ProviderBase +namespace Microsoft.Data.SqlClient.ConnectionPool { partial class DbConnectionPoolIdentity { @@ -43,10 +43,13 @@ private static DbConnectionPoolIdentity GetCurrentNative() string sidString = user.Value; // Win32NativeMethods.IsTokenRestricted will raise exception if the native call fails - bool isRestricted = Win32NativeMethods.IsTokenRestrictedWrapper(token); + SniNativeWrapper.SniIsTokenRestricted(token, out bool isRestricted); var lastIdentity = s_lastIdentity; - if ((lastIdentity != null) && (lastIdentity._sidString == sidString) && (lastIdentity._isRestricted == isRestricted) && (lastIdentity._isNetwork == isNetwork)) + if (lastIdentity != null && + lastIdentity._sidString == sidString && + lastIdentity._isRestricted == isRestricted && + lastIdentity._isNetwork == isNetwork) { current = lastIdentity; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolIdentity.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.cs similarity index 98% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolIdentity.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.cs index e97f55a7ad..5716146a6b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolIdentity.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolIdentity.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace Microsoft.Data.ProviderBase +namespace Microsoft.Data.SqlClient.ConnectionPool { sealed internal partial class DbConnectionPoolIdentity { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionPoolKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolKey.cs similarity index 96% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionPoolKey.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolKey.cs index 7d2799289f..bfcd4cd2ef 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionPoolKey.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolKey.cs @@ -4,7 +4,7 @@ using System; -namespace Microsoft.Data.Common +namespace Microsoft.Data.SqlClient.ConnectionPool { // DbConnectionPoolKey: Base class implementation of a key to connection pool groups // Only connection string is used as a key diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolOptions.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolOptions.cs similarity index 97% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolOptions.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolOptions.cs index 866453432c..7adf8abdc2 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolOptions.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolOptions.cs @@ -4,7 +4,7 @@ using System; -namespace Microsoft.Data.ProviderBase +namespace Microsoft.Data.SqlClient.ConnectionPool { internal sealed class DbConnectionPoolGroupOptions { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolProviderInfo.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolProviderInfo.cs similarity index 84% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolProviderInfo.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolProviderInfo.cs index 5392795dff..1afeb473be 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPoolProviderInfo.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolProviderInfo.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace Microsoft.Data.ProviderBase +namespace Microsoft.Data.SqlClient.ConnectionPool { internal class DbConnectionPoolProviderInfo { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolState.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolState.cs new file mode 100644 index 0000000000..1790e38a57 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/DbConnectionPoolState.cs @@ -0,0 +1,13 @@ +// 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. + +namespace Microsoft.Data.SqlClient.ConnectionPool +{ + internal enum DbConnectionPoolState + { + Initializing, + Running, + ShuttingDown, + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs new file mode 100644 index 0000000000..d6647f832e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/IDbConnectionPool.cs @@ -0,0 +1,150 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Concurrent; +using System.Data.Common; +using System.Threading.Tasks; +using System.Transactions; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.ProviderBase; + +namespace Microsoft.Data.SqlClient.ConnectionPool +{ + /// + /// A base interface for implementing database connection pools. + /// Implementations are responsible for managing the lifecycle + /// of connections and providing access to database connections. + /// + internal interface IDbConnectionPool + { + #region Properties + /// + /// Gets the authentication contexts cached by the pool. + /// + ConcurrentDictionary AuthenticationContexts { get; } + + /// + /// Gets the factory used to create database connections. + /// + DbConnectionFactory ConnectionFactory { get; } + + /// + /// The number of connections currently managed by the pool. + /// May be larger than the number of connections currently sitting idle in the pool. + /// + int Count { get; } + + /// + /// Indicates whether an error has occurred in the pool. + /// Primarily used to support the pool blocking period feature. + /// + bool ErrorOccurred { get; } + + /// + /// An id that uniqely identifies this connection pool. + /// + int Id { get; } + + /// + /// Gets the identity used by the connection pool when establishing connections. + /// + DbConnectionPoolIdentity Identity { get; } + + /// + /// Indicates whether the connection pool is currently running. + /// + bool IsRunning { get; } + + /// + /// Gets the duration of time to wait before reassigning a connection to a different server in a load-balanced + /// environment. + /// + TimeSpan LoadBalanceTimeout { get; } + + /// + /// Gets a reference to the connection pool group that this pool belongs to. + /// + DbConnectionPoolGroup PoolGroup { get; } + + /// + /// Gets the options for the connection pool group. + /// + DbConnectionPoolGroupOptions PoolGroupOptions { get; } + + /// + /// Gets the provider information for the connection pool. + /// + DbConnectionPoolProviderInfo ProviderInfo { get; } + + /// + /// The current state of the connection pool. + /// + DbConnectionPoolState State { get; set; } + + /// + /// Indicates whether the connection pool is using load balancing. + /// + bool UseLoadBalancing { get; } + #endregion + + #region Methods + /// + /// Clears the connection pool, releasing all connections and resetting the state. + /// + void Clear(); + + /// + /// Attempts to get a connection from the pool. + /// + /// The SqlConnection that will own this internal connection. + /// Used when calling this method in an async context. + /// The internal connection will be set on completion source rather than passed out via the out parameter. + /// The user options to use if a new connection must be opened. + /// The retrieved connection will be passed out via this parameter. + /// True if a connection was set in the out parameter, otherwise returns false. + bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal connection); + + /// + /// Replaces the internal connection currently associated with owningObject with a new internal connection from the pool. + /// + /// The connection whos internal connection should be replaced. + /// The user options to use if a new connection must be opened. + /// The internal connection currently associated with the owning object. + /// A reference to the new DbConnectionInternal. + DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection); + + /// + /// Returns an internal connection to the pool. + /// + /// The internal connection to return to the pool. + /// The connection that currently owns this internal connection. Used to verify ownership. + void ReturnInternalConnection(DbConnectionInternal obj, object owningObject); + + /// + /// Puts an internal connection from a transacted pool back into the general pool. + /// + /// The internal connection to return to the pool. + void PutObjectFromTransactedPool(DbConnectionInternal obj); + + /// + /// Initializes and starts the connection pool. Should be called once when the pool is created. + /// + void Startup(); + + /// + /// Shuts down the connection pool releasing any resources. Should be called once when the pool is no longer needed. + /// + void Shutdown(); + + /// + /// Informs the pool that a transaction has ended. The pool will commit and reset any internal + /// the transacted object associated with this transaction. + /// + /// The transaction that has ended. + /// The internal connection that should be committed and reset. + void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject); + #endregion + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionPoolGroupProviderInfo.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolGroupProviderInfo.cs similarity index 98% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionPoolGroupProviderInfo.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolGroupProviderInfo.cs index 47de649bbd..1fe1768c0a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionPoolGroupProviderInfo.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolGroupProviderInfo.cs @@ -3,9 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Security; -using Microsoft.Data.ProviderBase; -namespace Microsoft.Data.SqlClient +namespace Microsoft.Data.SqlClient.ConnectionPool { internal sealed class SqlConnectionPoolGroupProviderInfo : DbConnectionPoolGroupProviderInfo { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionPoolKey.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolKey.cs similarity index 98% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionPoolKey.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolKey.cs index 04a0d32281..207c0a8e1a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionPoolKey.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolKey.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using Microsoft.Data.Common; -namespace Microsoft.Data.SqlClient +namespace Microsoft.Data.SqlClient.ConnectionPool { // SqlConnectionPoolKey: Implementation of a key to connection pool groups for specifically to be used for SqlConnection // Connection string and SqlCredential are used as a key diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionPoolProviderInfo.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolProviderInfo.cs similarity index 89% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionPoolProviderInfo.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolProviderInfo.cs index eee7550f5e..9c6df4d245 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionPoolProviderInfo.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/SqlConnectionPoolProviderInfo.cs @@ -2,9 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.Data.ProviderBase; -namespace Microsoft.Data.SqlClient +namespace Microsoft.Data.SqlClient.ConnectionPool { internal sealed class SqlConnectionPoolProviderInfo : DbConnectionPoolProviderInfo { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs similarity index 87% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs index 336f690776..77d2b3bae1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/ProviderBase/DbConnectionPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ConnectionPool/WaitHandleDbConnectionPool.cs @@ -9,25 +9,52 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; using System.Transactions; using Microsoft.Data.Common; -using Microsoft.Data.SqlClient; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.ProviderBase; +using static Microsoft.Data.SqlClient.ConnectionPool.DbConnectionPoolState; -namespace Microsoft.Data.ProviderBase +namespace Microsoft.Data.SqlClient.ConnectionPool { - internal sealed class DbConnectionPool + /// + /// A concrete implementation of used by Microsoft.Data.SqlClient + /// to efficiently manage a pool of reusable objects backing ADO.NET SqlConnection instances. + /// + /// Primary Responsibilities: + /// + /// Connection Reuse and Pooling: Uses two stacks (_stackNew and _stackOld) to manage idle connections. Ensures efficient reuse and limits new connection creation. + /// Transaction-Aware Pooling: Tracks connections enlisted in using TransactedConnectionPool and TransactedConnectionList, ensuring proper context reuse. + /// Concurrency and Synchronization: Uses wait handles and semaphores via PoolWaitHandles to coordinate safe multi-threaded access. + /// Connection Lifecycle Management: Manages creation (CreateObject), deactivation (DeactivateObject), destruction (DestroyObject), and reclamation (ReclaimEmancipatedObjects) of internal connections. + /// Error Handling and Resilience: Implements retry and exponential backoff in TryGetConnection and handles transient errors using _errorWait. + /// Minimum Pool Size Enforcement: Maintains the MinPoolSize by spawning background tasks to create new connections when needed. + /// Load Balancing Support: Honors LoadBalanceTimeout to clean up idle connections and distribute load evenly. + /// Telemetry and Tracing: Uses SqlClientEventSource for extensive diagnostic tracing of connection lifecycle events. + /// Pending Request Queue: Queues unresolved connection requests in _pendingOpens and processes them using background threads. + /// Identity and Authentication Context: Manages identity-based reuse via a dictionary of DbConnectionPoolAuthenticationContext keyed by user identity. + /// + /// + /// Key Concepts in Design: + /// + /// Stacks and queues for free and pending connections + /// Synchronization via WaitHandle, Semaphore, and ManualResetEvent + /// Support for transaction enlistment and affinity + /// Timer-based cleanup to prune idle or expired connections + /// Background thread spawning for servicing deferred requests and replenishing the pool + /// + /// + internal sealed class WaitHandleDbConnectionPool : IDbConnectionPool { - private enum State - { - Initializing, - Running, - ShuttingDown, - } + + private static int _objectTypeCount; + + public int Id => Interlocked.Increment(ref _objectTypeCount); + + public DbConnectionPoolState State { get; set; } // This class is a way to stash our cloned Tx key for later disposal when it's no longer needed. // We can't get at the key in the dictionary without enumerating entries, so we stash an extra @@ -68,18 +95,18 @@ private sealed class TransactedConnectionPool { Dictionary _transactedCxns; - DbConnectionPool _pool; + IDbConnectionPool _pool; private static int _objectTypeCount; // EventSource Counter internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); - internal TransactedConnectionPool(DbConnectionPool pool) + internal TransactedConnectionPool(IDbConnectionPool pool) { Debug.Assert(pool != null, "null pool?"); _pool = pool; _transactedCxns = new Dictionary(); - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Constructed for connection pool {1}", ObjectID, _pool.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Constructed for connection pool {1}", ObjectID, _pool.Id); } internal int ObjectID @@ -90,7 +117,7 @@ internal int ObjectID } } - internal DbConnectionPool Pool + internal IDbConnectionPool Pool { get { @@ -236,11 +263,8 @@ internal void PutTransactedObject(Transaction transaction, DbConnectionInternal } SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Connection {2}, Added.", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID); } -#if NET - SqlClientEventSource.Log.EnterFreeConnection(); -#else - Pool.PerformanceCounters.NumberOfFreeConnections.Increment(); -#endif + + SqlClientEventSource.Metrics.EnterFreeConnection(); } internal void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject) @@ -303,11 +327,8 @@ internal void TransactionEnded(Transaction transaction, DbConnectionInternal tra // connections, we'll put it back... if (0 <= entry) { -#if NET - SqlClientEventSource.Log.ExitFreeConnection(); -#else - Pool.PerformanceCounters.NumberOfFreeConnections.Decrement(); -#endif + + SqlClientEventSource.Metrics.ExitFreeConnection(); Pool.PutObjectFromTransactedPool(transactedObject); } } @@ -362,17 +383,17 @@ internal WaitHandle[] GetHandles(bool withCreate) } } - private const int MAX_Q_SIZE = (int)0x00100000; + private const int MAX_Q_SIZE = 0x00100000; // The order of these is important; we want the WaitAny call to be signaled // for a free object before a creation signal. Only the index first signaled // object is returned from the WaitAny call. - private const int SEMAPHORE_HANDLE = (int)0x0; - private const int ERROR_HANDLE = (int)0x1; - private const int CREATION_HANDLE = (int)0x2; - private const int BOGUS_HANDLE = (int)0x3; + private const int SEMAPHORE_HANDLE = 0x0; + private const int ERROR_HANDLE = 0x1; + private const int CREATION_HANDLE = 0x2; + private const int BOGUS_HANDLE = 0x3; - private const int WAIT_ABANDONED = (int)0x80; + private const int WAIT_ABANDONED = 0x80; private const int ERROR_WAIT_DEFAULT = 5 * 1000; // 5 seconds @@ -392,8 +413,6 @@ internal WaitHandle[] GetHandles(bool withCreate) /// private readonly ConcurrentDictionary _pooledDbAuthenticationContexts; - private State _state; - private readonly ConcurrentStack _stackOld = new ConcurrentStack(); private readonly ConcurrentStack _stackNew = new ConcurrentStack(); @@ -418,11 +437,8 @@ internal WaitHandle[] GetHandles(bool withCreate) private readonly List _objectList; private int _totalObjects; - private static int _objectTypeCount; // EventSource counter - internal readonly int _objectID = System.Threading.Interlocked.Increment(ref _objectTypeCount); - // only created by DbConnectionPoolGroup.GetConnectionPool - internal DbConnectionPool( + internal WaitHandleDbConnectionPool( DbConnectionFactory connectionFactory, DbConnectionPoolGroup connectionPoolGroup, DbConnectionPoolIdentity identity, @@ -435,7 +451,7 @@ internal DbConnectionPool( throw ADP.InternalError(ADP.InternalErrorCode.AttemptingToPoolOnRestrictedToken); } - _state = State.Initializing; + State = Initializing; lock (s_random) { @@ -462,8 +478,8 @@ internal DbConnectionPool( _transactedConnectionPool = new TransactedConnectionPool(this); _poolCreateRequest = new WaitCallback(PoolCreateRequest); // used by CleanupCallback - _state = State.Running; - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Constructed.", ObjectID); + State = Running; + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Constructed.", Id); //_cleanupTimer & QueuePoolCreateRequest is delayed until DbConnectionPoolGroup calls // StartBackgroundCallbacks after pool is actually in the collection @@ -474,36 +490,21 @@ private int CreationTimeout get { return PoolGroupOptions.CreationTimeout; } } - internal int Count - { - get { return _totalObjects; } - } + public int Count => _totalObjects; - internal DbConnectionFactory ConnectionFactory - { - get { return _connectionFactory; } - } + public DbConnectionFactory ConnectionFactory => _connectionFactory; - internal bool ErrorOccurred - { - get { return _errorOccurred; } - } + public bool ErrorOccurred => _errorOccurred; - private bool HasTransactionAffinity - { - get { return PoolGroupOptions.HasTransactionAffinity; } - } + private bool HasTransactionAffinity => PoolGroupOptions.HasTransactionAffinity; - internal TimeSpan LoadBalanceTimeout - { - get { return PoolGroupOptions.LoadBalanceTimeout; } - } + public TimeSpan LoadBalanceTimeout => PoolGroupOptions.LoadBalanceTimeout; private bool NeedToReplenish { get { - if (State.Running != _state) // Don't allow connection create when not running. + if (State is not Running) // Don't allow connection create when not running. return false; int totalObjects = Count; @@ -514,7 +515,7 @@ private bool NeedToReplenish if (totalObjects < MinPoolSize) return true; - int freeObjects = (_stackNew.Count + _stackOld.Count); + int freeObjects = _stackNew.Count + _stackOld.Count; int waitingRequests = _waitCount; bool needToReplenish = (freeObjects < waitingRequests) || ((freeObjects == waitingRequests) && (totalObjects > 1)); @@ -522,76 +523,31 @@ private bool NeedToReplenish } } - internal DbConnectionPoolIdentity Identity - { - get { return _identity; } - } - - internal bool IsRunning - { - get { return State.Running == _state; } - } - - private int MaxPoolSize - { - get { return PoolGroupOptions.MaxPoolSize; } - } + public DbConnectionPoolIdentity Identity => _identity; - private int MinPoolSize + public bool IsRunning { - get { return PoolGroupOptions.MinPoolSize; } + get { return State is Running; } } - internal int ObjectID - { - get - { - return _objectID; - } - } + private int MaxPoolSize => PoolGroupOptions.MaxPoolSize; -#if NETFRAMEWORK - internal DbConnectionPoolCounters PerformanceCounters - { - get { return _connectionFactory.PerformanceCounters; } - } -#endif + private int MinPoolSize => PoolGroupOptions.MinPoolSize; - internal DbConnectionPoolGroup PoolGroup - { - get { return _connectionPoolGroup; } - } + public DbConnectionPoolGroup PoolGroup => _connectionPoolGroup; - internal DbConnectionPoolGroupOptions PoolGroupOptions - { - get { return _connectionPoolGroupOptions; } - } + public DbConnectionPoolGroupOptions PoolGroupOptions => _connectionPoolGroupOptions; - internal DbConnectionPoolProviderInfo ProviderInfo - { - get { return _connectionPoolProviderInfo; } - } + public DbConnectionPoolProviderInfo ProviderInfo => _connectionPoolProviderInfo; /// /// Return the pooled authentication contexts. /// - internal ConcurrentDictionary AuthenticationContexts - { - get - { - return _pooledDbAuthenticationContexts; - } - } + public ConcurrentDictionary AuthenticationContexts => _pooledDbAuthenticationContexts; - internal bool UseLoadBalancing - { - get { return PoolGroupOptions.UseLoadBalancing; } - } + public bool UseLoadBalancing => PoolGroupOptions.UseLoadBalancing; - private bool UsingIntegrateSecurity - { - get { return _identity != null && DbConnectionPoolIdentity.NoIdentity != _identity; } - } + private bool UsingIntegrateSecurity => _identity != null && DbConnectionPoolIdentity.NoIdentity != _identity; private void CleanupCallback(object state) { @@ -613,11 +569,11 @@ private void CleanupCallback(object state) // // With this logic, objects are pruned from the pool if unused for // at least one period but not more than two periods. - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", Id); // Destroy free objects that put us above MinPoolSize from old stack. while (Count > MinPoolSize) - { + { // While above MinPoolSize... if (_waitHandles.PoolSemaphore.WaitOne(0, false)) { @@ -628,11 +584,8 @@ private void CleanupCallback(object state) { Debug.Assert(obj != null, "null connection is not expected"); // If we obtained one from the old stack, destroy it. -#if NET - SqlClientEventSource.Log.ExitFreeConnection(); -#else - PerformanceCounters.NumberOfFreeConnections.Decrement(); -#endif + + SqlClientEventSource.Metrics.ExitFreeConnection(); // Transaction roots must survive even aging out (TxEnd event will clean them up). bool shouldDestroy = true; @@ -691,7 +644,7 @@ private void CleanupCallback(object state) break; Debug.Assert(obj != null, "null connection is not expected"); - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, ChangeStacks={1}", ObjectID, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, ChangeStacks={1}", Id, obj.ObjectID); Debug.Assert(!obj.IsEmancipated, "pooled object not in pool"); Debug.Assert(obj.CanBePooled, "pooled object is not poolable"); @@ -704,9 +657,9 @@ private void CleanupCallback(object state) QueuePoolCreateRequest(); } - internal void Clear() + public void Clear() { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Clearing.", ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Clearing.", Id); DbConnectionInternal obj; // First, quickly doom everything. @@ -729,28 +682,22 @@ internal void Clear() while (_stackNew.TryPop(out obj)) { Debug.Assert(obj != null, "null connection is not expected"); -#if NET - SqlClientEventSource.Log.ExitFreeConnection(); -#else - PerformanceCounters.NumberOfFreeConnections.Decrement(); -#endif + + SqlClientEventSource.Metrics.ExitFreeConnection(); DestroyObject(obj); } while (_stackOld.TryPop(out obj)) { Debug.Assert(obj != null, "null connection is not expected"); -#if NET - SqlClientEventSource.Log.ExitFreeConnection(); -#else - PerformanceCounters.NumberOfFreeConnections.Decrement(); -#endif + + SqlClientEventSource.Metrics.ExitFreeConnection(); DestroyObject(obj); } // Finally, reclaim everything that's emancipated (which, because // it's been doomed, will cause it to be disposed of as well) ReclaimEmancipatedObjects(); - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Cleared.", ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Cleared.", Id); } private Timer CreateCleanupTimer() => @@ -818,28 +765,11 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectio } _objectList.Add(newObj); _totalObjects = _objectList.Count; -#if NET - SqlClientEventSource.Log.EnterPooledConnection(); -#else - PerformanceCounters.NumberOfPooledConnections.Increment(); // TODO: Performance: Consider moving outside of lock? -#endif - } - // If the old connection belonged to another pool, we need to remove it from that - if (oldConnection != null) - { - var oldConnectionPool = oldConnection.Pool; - if (oldConnectionPool != null && oldConnectionPool != this) - { - Debug.Assert(oldConnectionPool._state == State.ShuttingDown, "Old connections pool should be shutting down"); - lock (oldConnectionPool._objectList) - { - oldConnectionPool._objectList.Remove(oldConnection); - oldConnectionPool._totalObjects = oldConnectionPool._objectList.Count; - } - } + SqlClientEventSource.Metrics.EnterPooledConnection(); } - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Added to pool.", ObjectID, newObj?.ObjectID); + + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Added to pool.", Id, newObj?.ObjectID); // Reset the error wait: _errorWait = ERROR_WAIT_DEFAULT; @@ -910,7 +840,7 @@ private DbConnectionInternal CreateObject(DbConnection owningObject, DbConnectio private void DeactivateObject(DbConnectionInternal obj) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Deactivating.", ObjectID, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Deactivating.", Id, obj.ObjectID); obj.DeactivateConnection(); bool returnToGeneralPool = false; @@ -926,7 +856,7 @@ private void DeactivateObject(DbConnectionInternal obj) { // NOTE: constructor should ensure that current state cannot be State.Initializing, so it can only // be State.Running or State.ShuttingDown - Debug.Assert(_state == State.Running || _state == State.ShuttingDown); + Debug.Assert(State is Running or ShuttingDown); lock (obj) { @@ -936,7 +866,7 @@ private void DeactivateObject(DbConnectionInternal obj) // transaction object will ensure that it is owned (not lost), // and it will be certain to put it back into the pool. - if (_state == State.ShuttingDown) + if (State is ShuttingDown) { if (obj.IsTransactionRoot) { @@ -1039,7 +969,7 @@ private void DeactivateObject(DbConnectionInternal obj) Debug.Assert(rootTxn == true || returnToGeneralPool == true || destroyObject == true); } - internal void DestroyObject(DbConnectionInternal obj) + private void DestroyObject(DbConnectionInternal obj) { // A connection with a delegated transaction cannot be disposed of // until the delegated transaction has actually completed. Instead, @@ -1048,11 +978,11 @@ internal void DestroyObject(DbConnectionInternal obj) // again. if (obj.IsTxRootWaitingForTxEnd) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Has Delegated Transaction, waiting to Dispose.", ObjectID, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Has Delegated Transaction, waiting to Dispose.", Id, obj.ObjectID); } else { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Removing from pool.", ObjectID, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Removing from pool.", Id, obj.ObjectID); bool removed = false; lock (_objectList) { @@ -1063,26 +993,20 @@ internal void DestroyObject(DbConnectionInternal obj) if (removed) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Removed from pool.", ObjectID, obj.ObjectID); -#if NET - SqlClientEventSource.Log.ExitPooledConnection(); -#else - PerformanceCounters.NumberOfPooledConnections.Decrement(); -#endif + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Removed from pool.", Id, obj.ObjectID); + + SqlClientEventSource.Metrics.ExitPooledConnection(); } obj.Dispose(); - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Disposed.", ObjectID, obj.ObjectID); -#if NET - SqlClientEventSource.Log.HardDisconnectRequest(); -#else - PerformanceCounters.HardDisconnectsPerSecond.Increment(); -#endif + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Disposed.", Id, obj.ObjectID); + + SqlClientEventSource.Metrics.HardDisconnectRequest(); } } private void ErrorCallback(object state) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Resetting Error handling.", ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Resetting Error handling.", Id); _errorOccurred = false; _waitHandles.ErrorEvent.Reset(); @@ -1158,29 +1082,14 @@ private void WaitForPendingOpen() #endif try { -#if DEBUG - Microsoft.Data.SqlClient.TdsParser.ReliabilitySection tdsReliabilitySection = new Microsoft.Data.SqlClient.TdsParser.ReliabilitySection(); - -#if NETFRAMEWORK - RuntimeHelpers.PrepareConstrainedRegions(); -#endif - try - { - tdsReliabilitySection.Start(); -#else - { -#endif //DEBUG - bool allowCreate = true; - bool onlyOneCheckConnection = false; - ADP.SetCurrentTransaction(next.Completion.Task.AsyncState as Transaction); - timeout = !TryGetConnection(next.Owner, delay, allowCreate, onlyOneCheckConnection, next.UserOptions, out connection); - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG + ADP.SetCurrentTransaction(next.Completion.Task.AsyncState as Transaction); + timeout = !TryGetConnection( + next.Owner, + delay, + allowCreate: true, + onlyOneCheckConnection: false, + next.UserOptions, + out connection); } catch (System.OutOfMemoryException) { @@ -1219,7 +1128,7 @@ private void WaitForPendingOpen() if (!next.Completion.TrySetResult(connection)) { // if the completion was cancelled, lets try and get this connection back for the next try - PutObject(connection, next.Owner); + ReturnInternalConnection(connection, next.Owner); } } } @@ -1234,12 +1143,12 @@ private void WaitForPendingOpen() } while (_pendingOpens.TryPeek(out next)); } - internal bool TryGetConnection(DbConnection owningObject, TaskCompletionSource retry, DbConnectionOptions userOptions, out DbConnectionInternal connection) + public bool TryGetConnection(DbConnection owningObject, TaskCompletionSource taskCompletionSource, DbConnectionOptions userOptions, out DbConnectionInternal connection) { uint waitForMultipleObjectsTimeout = 0; bool allowCreate = false; - if (retry == null) + if (taskCompletionSource == null) { waitForMultipleObjectsTimeout = (uint)CreationTimeout; @@ -1250,9 +1159,9 @@ internal bool TryGetConnection(DbConnection owningObject, TaskCompletionSource {0}, DbConnectionInternal State != Running.", ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, DbConnectionInternal State != Running.", Id); connection = null; return true; } @@ -1262,7 +1171,7 @@ internal bool TryGetConnection(DbConnection owningObject, TaskCompletionSource {0}, Getting connection.", ObjectID); + + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Getting connection.", Id); // If automatic transaction enlistment is required, then we try to // get the connection from the transacted connection pool first. @@ -1343,19 +1250,19 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj switch (waitResult) { case WaitHandle.WaitTimeout: - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Wait timed out.", ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Wait timed out.", Id); Interlocked.Decrement(ref _waitCount); connection = null; return false; case ERROR_HANDLE: // Throw the error that PoolCreateRequest stashed. - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Errors are set.", ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Errors are set.", Id); Interlocked.Decrement(ref _waitCount); throw TryCloneCachedException(); case CREATION_HANDLE: - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", Id); try { obj = UserCreateRequest(owningObject, userOptions); @@ -1408,7 +1315,7 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj if ((obj != null) && (!obj.IsConnectionAlive())) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", ObjectID, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", Id, obj.ObjectID); DestroyObject(obj); obj = null; // Setting to null in case creating a new object fails @@ -1421,7 +1328,7 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj #endif try { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creating new connection.", Id); obj = UserCreateRequest(owningObject, userOptions); } finally @@ -1432,7 +1339,7 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj else { // Timeout waiting for creation semaphore - return null - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Wait timed out.", ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Wait timed out.", Id); connection = null; return false; } @@ -1440,23 +1347,23 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj } break; - case (WAIT_ABANDONED + SEMAPHORE_HANDLE): - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Semaphore handle abandonded.", ObjectID); + case WAIT_ABANDONED + SEMAPHORE_HANDLE: + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Semaphore handle abandonded.", Id); Interlocked.Decrement(ref _waitCount); throw new AbandonedMutexException(SEMAPHORE_HANDLE, _waitHandles.PoolSemaphore); - case (WAIT_ABANDONED + ERROR_HANDLE): - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Error handle abandonded.", ObjectID); + case WAIT_ABANDONED + ERROR_HANDLE: + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Error handle abandonded.", Id); Interlocked.Decrement(ref _waitCount); throw new AbandonedMutexException(ERROR_HANDLE, _waitHandles.ErrorEvent); - case (WAIT_ABANDONED + CREATION_HANDLE): - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creation handle abandoned.", ObjectID); + case WAIT_ABANDONED + CREATION_HANDLE: + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Creation handle abandoned.", Id); Interlocked.Decrement(ref _waitCount); throw new AbandonedMutexException(CREATION_HANDLE, _waitHandles.CreationSemaphore); default: - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, WaitForMultipleObjects={1}", ObjectID, waitResult); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, WaitForMultipleObjects={1}", Id, waitResult); Interlocked.Decrement(ref _waitCount); throw ADP.InternalError(ADP.InternalErrorCode.UnexpectedWaitAnyResult); } @@ -1484,9 +1391,9 @@ private bool TryGetConnection(DbConnection owningObject, uint waitForMultipleObj } connection = obj; -#if NET - SqlClientEventSource.Log.SoftConnectRequest(); -#endif + + SqlClientEventSource.Metrics.SoftConnectRequest(); + return true; } @@ -1504,7 +1411,7 @@ private void PrepareConnection(DbConnection owningObject, DbConnectionInternal o { // if Activate throws an exception // put it back in the pool or have it properly disposed of - this.PutObject(obj, owningObject); + this.ReturnInternalConnection(obj, owningObject); throw; } } @@ -1516,19 +1423,14 @@ private void PrepareConnection(DbConnection owningObject, DbConnectionInternal o /// Options used to create the new connection /// Inner connection that will be replaced /// A new inner connection that is attached to the - internal DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) + public DbConnectionInternal ReplaceConnection(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection) { -#if NETFRAMEWORK - PerformanceCounters.SoftConnectsPerSecond.Increment(); -#endif - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, replacing connection.", ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, replacing connection.", Id); DbConnectionInternal newConnection = UserCreateRequest(owningObject, userOptions, oldConnection); if (newConnection != null) { -#if NET - SqlClientEventSource.Log.SoftConnectRequest(); -#endif + SqlClientEventSource.Metrics.SoftConnectRequest(); PrepareConnection(owningObject, newConnection, oldConnection.EnlistedTransaction); oldConnection.PrepareForReplaceConnection(); oldConnection.DeactivateConnection(); @@ -1565,14 +1467,11 @@ private DbConnectionInternal GetFromGeneralPool() if (obj != null) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Popped from general pool.", ObjectID, obj.ObjectID); -#if NET - SqlClientEventSource.Log.ExitFreeConnection(); -#else - PerformanceCounters.NumberOfFreeConnections.Decrement(); -#endif + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Popped from general pool.", Id, obj.ObjectID); + + SqlClientEventSource.Metrics.ExitFreeConnection(); } - return (obj); + return obj; } private DbConnectionInternal GetFromTransactedPool(out Transaction transaction) @@ -1586,12 +1485,9 @@ private DbConnectionInternal GetFromTransactedPool(out Transaction transaction) if (obj != null) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Popped from transacted pool.", ObjectID, obj.ObjectID); -#if NET - SqlClientEventSource.Log.ExitFreeConnection(); -#else - PerformanceCounters.NumberOfFreeConnections.Decrement(); -#endif + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Popped from transacted pool.", Id, obj.ObjectID); + + SqlClientEventSource.Metrics.ExitFreeConnection(); if (obj.IsTransactionRoot) { @@ -1601,14 +1497,14 @@ private DbConnectionInternal GetFromTransactedPool(out Transaction transaction) } catch { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", ObjectID, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", Id, obj.ObjectID); DestroyObject(obj); throw; } } else if (!obj.IsConnectionAlive()) { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", ObjectID, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, found dead and removed.", Id, obj.ObjectID); DestroyObject(obj); obj = null; } @@ -1625,10 +1521,10 @@ private void PoolCreateRequest(object state) { // called by pooler to ensure pool requests are currently being satisfied - // creation mutex has not been obtained - long scopeID = SqlClientEventSource.Log.TryPoolerScopeEnterEvent(" {0}", ObjectID); + long scopeID = SqlClientEventSource.Log.TryPoolerScopeEnterEvent(" {0}", Id); try { - if (State.Running == _state) + if (State is Running) { // in case WaitForPendingOpen ever failed with no subsequent OpenAsync calls, // start it back up again @@ -1719,7 +1615,7 @@ private void PoolCreateRequest(object state) else { // trace waitResult and ignore the failure - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, PoolCreateRequest called WaitForSingleObject failed {1}", ObjectID, waitResult); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, PoolCreateRequest called WaitForSingleObject failed {1}", Id, waitResult); } } catch (Exception e) @@ -1732,7 +1628,7 @@ private void PoolCreateRequest(object state) // Now that CreateObject can throw, we need to catch the exception and discard it. // There is no further action we can take beyond tracing. The error will be // thrown to the user the next time they request a connection. - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, PoolCreateRequest called CreateConnection which threw an exception: {1}", ObjectID, e); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, PoolCreateRequest called CreateConnection which threw an exception: {1}", Id, e); } finally { @@ -1752,31 +1648,24 @@ private void PoolCreateRequest(object state) } } - internal void PutNewObject(DbConnectionInternal obj) + private void PutNewObject(DbConnectionInternal obj) { Debug.Assert(obj != null, "why are we adding a null object to the pool?"); - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Pushing to general pool.", ObjectID, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Pushing to general pool.", Id, obj.ObjectID); _stackNew.Push(obj); _waitHandles.PoolSemaphore.Release(1); -#if NET - SqlClientEventSource.Log.EnterFreeConnection(); -#else - PerformanceCounters.NumberOfFreeConnections.Increment(); -#endif + + SqlClientEventSource.Metrics.EnterFreeConnection(); } - internal void PutObject(DbConnectionInternal obj, object owningObject) + public void ReturnInternalConnection(DbConnectionInternal obj, object owningObject) { Debug.Assert(obj != null, "null obj?"); -#if NET - SqlClientEventSource.Log.SoftDisconnectRequest(); -#else - PerformanceCounters.SoftDisconnectsPerSecond.Increment(); -#endif + SqlClientEventSource.Metrics.SoftDisconnectRequest(); // Once a connection is closing (which is the state that we're in at // this point in time) you cannot delegate a transaction to or enlist @@ -1800,11 +1689,13 @@ internal void PutObject(DbConnectionInternal obj, object owningObject) DeactivateObject(obj); } - internal void PutObjectFromTransactedPool(DbConnectionInternal obj) + public void PutObjectFromTransactedPool(DbConnectionInternal obj) { Debug.Assert(obj != null, "null pooledObject?"); Debug.Assert(obj.EnlistedTransaction == null, "pooledObject is still enlisted?"); + obj.DeactivateConnection(); + // called by the transacted connection pool , once it's removed the // connection from it's list. We put the connection back in general // circulation. @@ -1813,9 +1704,9 @@ internal void PutObjectFromTransactedPool(DbConnectionInternal obj) // method, we can safely presume that the caller is the only person // that is using the connection, and that all pre-push logic has been // done and all transactions are ended. - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Transaction has ended.", ObjectID, obj.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Transaction has ended.", Id, obj.ObjectID); - if (_state == State.Running && obj.CanBePooled) + if (State is Running && obj.CanBePooled) { PutNewObject(obj); } @@ -1828,7 +1719,7 @@ internal void PutObjectFromTransactedPool(DbConnectionInternal obj) private void QueuePoolCreateRequest() { - if (State.Running == _state) + if (State is Running) { // Make sure we're at quota by posting a callback to the threadpool. ThreadPool.QueueUserWorkItem(_poolCreateRequest); @@ -1838,7 +1729,7 @@ private void QueuePoolCreateRequest() private bool ReclaimEmancipatedObjects() { bool emancipatedObjectFound = false; - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", Id); List reclaimedObjects = new List(); int count; @@ -1890,12 +1781,9 @@ private bool ReclaimEmancipatedObjects() for (int i = 0; i < count; ++i) { DbConnectionInternal obj = reclaimedObjects[i]; - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Reclaiming.", ObjectID, obj.ObjectID); -#if NET - SqlClientEventSource.Log.ReclaimedConnectionRequest(); -#else - PerformanceCounters.NumberOfReclaimedConnections.Increment(); -#endif + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Connection {1}, Reclaiming.", Id, obj.ObjectID); + + SqlClientEventSource.Metrics.ReclaimedConnectionRequest(); emancipatedObjectFound = true; @@ -1905,9 +1793,9 @@ private bool ReclaimEmancipatedObjects() return emancipatedObjectFound; } - internal void Startup() + public void Startup() { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, CleanupWait={1}", ObjectID, _cleanupWait); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, CleanupWait={1}", Id, _cleanupWait); _cleanupTimer = CreateCleanupTimer(); if (NeedToReplenish) @@ -1916,10 +1804,10 @@ internal void Startup() } } - internal void Shutdown() + public void Shutdown() { - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", ObjectID); - _state = State.ShuttingDown; + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}", Id); + State = ShuttingDown; // deactivate timer callbacks Timer t = _cleanupTimer; @@ -1934,13 +1822,13 @@ internal void Shutdown() // that is implemented inside DbConnectionPool. This method's counterpart (PutTransactedObject) should // only be called from DbConnectionPool.DeactivateObject and thus the plumbing to provide access to // other objects is unnecessary (hence the asymmetry of Ended but no Begin) - internal void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject) + public void TransactionEnded(Transaction transaction, DbConnectionInternal transactedObject) { Debug.Assert(transaction != null, "null transaction?"); Debug.Assert(transactedObject != null, "null transactedObject?"); // Note: connection may still be associated with transaction due to Explicit Unbinding requirement. - SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Connection {2}, Transaction Completed", ObjectID, transaction.GetHashCode(), transactedObject.ObjectID); + SqlClientEventSource.Log.TryPoolerTraceEvent(" {0}, Transaction {1}, Connection {2}, Transaction Completed", Id, transaction.GetHashCode(), transactedObject.ObjectID); // called by the internal connection when it get's told that the // transaction is completed. We tell the transacted pool to remove diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/DiagnosticScope.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/DiagnosticScope.netcore.cs new file mode 100644 index 0000000000..7033eee9d8 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/DiagnosticScope.netcore.cs @@ -0,0 +1,110 @@ +// 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. + +#if NET + +using System; +using System.Runtime.CompilerServices; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + internal ref struct DiagnosticScope //: IDisposable + { + private const int CommandOperation = 1; + private const int ConnectionOpenOperation = 2; + + private readonly object _context1; + private readonly object _context2; + private readonly SqlDiagnosticListener _diagnostics; + private readonly int _operation; + private readonly Guid _operationId; + private readonly string _operationName; + + private Exception _exception; + + private DiagnosticScope( + SqlDiagnosticListener diagnostics, + int operation, + Guid operationsId, + string operationName, + object context1, + object context2) + { + _diagnostics = diagnostics; + _operation = operation; + _operationId = operationsId; + _operationName = operationName; + _context1 = context1; + _context2 = context2; + _exception = null; + } + + public static DiagnosticScope CreateCommandScope( + SqlDiagnosticListener diagnostics, + SqlCommand command, + SqlTransaction transaction, + [CallerMemberName] + string operationName = "") + { + Guid operationId = diagnostics.WriteCommandBefore(command, transaction, operationName); + return new DiagnosticScope(diagnostics, CommandOperation, operationId, operationName, command, transaction); + } + + // Although ref structs do not allow for inheriting from interfaces (< C#13), but the + // compiler will know to treat this like an IDisposable (> C# 8) + public void Dispose() + { + switch (_operation) + { + case CommandOperation: + if (_exception != null) + { + _diagnostics.WriteCommandError( + _operationId, + (SqlCommand)_context1, + (SqlTransaction)_context2, + _exception, + _operationName); + } + else + { + _diagnostics.WriteCommandAfter( + _operationId, + (SqlCommand)_context1, + (SqlTransaction)_context2, + _operationName); + } + break; + + case ConnectionOpenOperation: + if (_exception != null) + { + _diagnostics.WriteConnectionOpenError( + _operationId, + (SqlConnection)_context1, + _exception, + _operationName); + } + else + { + _diagnostics.WriteConnectionOpenAfter( + _operationId, + (SqlConnection)_context1, + _operationName); + } + break; + + // ConnectionCloseOperation is not implemented because it is conditionally + // emitted and that requires manual calls to the write apis + } + } + + public void SetException(Exception ex) + { + _exception = ex; + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/DiagnosticTransactionScope.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/DiagnosticTransactionScope.netcore.cs new file mode 100644 index 0000000000..a0ffe49587 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/DiagnosticTransactionScope.netcore.cs @@ -0,0 +1,161 @@ +// 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. + +#if NET + +using System; +using System.Data; +using System.Runtime.CompilerServices; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + internal ref struct DiagnosticTransactionScope //: IDisposable + { + public const int TransactionCommit = 1; + public const int TransactionRollback = 2; + + private readonly SqlConnection _connection; + private readonly SqlDiagnosticListener _diagnostics; + private readonly int _operation; + private readonly Guid _operationId; + private readonly string _operationName; + private readonly IsolationLevel _isolationLevel; + private readonly SqlInternalTransaction _transaction; + private readonly string _transactionName; + + private Exception _exception; + + public DiagnosticTransactionScope( + SqlDiagnosticListener diagnostics, + int operation, + Guid operationId, + string operationName, + IsolationLevel isolationLevel, + SqlConnection connection, + SqlInternalTransaction transaction, + string transactionName) + { + _diagnostics = diagnostics; + _operation = operation; + _operationId = operationId; + _operationName = operationName; + _isolationLevel = isolationLevel; + _connection = connection; + _transaction = transaction; + _transactionName = transactionName; + _exception = null; + } + + public static DiagnosticTransactionScope CreateTransactionCommitScope( + SqlDiagnosticListener diagnostics, + IsolationLevel isolationLevel, + SqlConnection connection, + SqlInternalTransaction transaction, + [CallerMemberName] + string operationName = "") + { + Guid operationId = diagnostics.WriteTransactionCommitBefore( + isolationLevel, + connection, + transaction, + operationName); + return new DiagnosticTransactionScope( + diagnostics, + TransactionCommit, + operationId, + operationName, + isolationLevel, + connection, + transaction, + transactionName: null); + } + + public static DiagnosticTransactionScope CreateTransactionRollbackScope( + SqlDiagnosticListener diagnostics, + IsolationLevel isolationLevel, + SqlConnection connection, + SqlInternalTransaction transaction, + string transactionName, + [CallerMemberName] + string operationName = "") + { + Guid operationId = diagnostics.WriteTransactionRollbackBefore( + isolationLevel, + connection, + transaction, + transactionName, + operationName); + return new DiagnosticTransactionScope( + diagnostics, + TransactionCommit, + operationId, + operationName, + isolationLevel, + connection, + transaction, + transactionName); + } + + // Although ref structs do not allow for inheriting from interfaces (< C#13), but the + // compiler will know to treat this like an IDisposable (> C# 8) + public void Dispose() + { + switch (_operation) + { + case TransactionCommit: + if (_exception != null) + { + _diagnostics.WriteTransactionCommitError( + _operationId, + _isolationLevel, + _connection, + _transaction, + _exception, + _operationName); + } + else + { + _diagnostics.WriteTransactionCommitAfter( + _operationId, + _isolationLevel, + _connection, + _transaction, + _operationName); + } + break; + + case TransactionRollback: + if (_exception != null) + { + _diagnostics.WriteTransactionRollbackError( + _operationId, + _isolationLevel, + _connection, + _transaction, + _exception, + _transactionName, + _operationName); + } + else + { + _diagnostics.WriteTransactionRollbackAfter( + _operationId, + _isolationLevel, + _connection, + _transaction, + _transactionName, + _operationName); + } + break; + } + } + + public void SetException(Exception ex) + { + _exception = ex; + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientCommandAfter.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientCommandAfter.netcore.cs new file mode 100644 index 0000000000..198b025779 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientCommandAfter.netcore.cs @@ -0,0 +1,86 @@ +// 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. + +#if NET + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + /// + public sealed class SqlClientCommandAfter : IReadOnlyList> + { + /// + public const string Name = "Microsoft.Data.SqlClient.WriteCommandAfter"; + + internal SqlClientCommandAfter( + Guid operationId, + string operation, + long timestamp, + Guid? connectionId, + long? transactionId, + SqlCommand command, + IDictionary statistics) + { + OperationId = operationId; + Operation = operation; + Timestamp = timestamp; + ConnectionId = connectionId; + TransactionId = transactionId; + Command = command; + Statistics = statistics; + } + + /// + public Guid OperationId { get; } + /// + public string Operation { get; } + /// + public long Timestamp { get; } + /// + public Guid? ConnectionId { get; } + /// + public long? TransactionId { get; } + /// + public SqlCommand Command { get; } + /// + public IDictionary Statistics { get; } + + /// + public int Count => 3 + 4; + + /// + public KeyValuePair this[int index] + { + get => index switch + { + 0 => new KeyValuePair(nameof(OperationId), OperationId), + 1 => new KeyValuePair(nameof(Operation), Operation), + 2 => new KeyValuePair(nameof(Timestamp), Timestamp), + 3 => new KeyValuePair(nameof(ConnectionId), ConnectionId), + 4 => new KeyValuePair(nameof(TransactionId), TransactionId), + 5 => new KeyValuePair(nameof(Command), Command), + 6 => new KeyValuePair(nameof(Statistics), Statistics), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public IEnumerator> GetEnumerator() + { + int count = Count; + for (int index = 0; index < count; index++) + { + yield return this[index]; + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientCommandBefore.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientCommandBefore.netcore.cs new file mode 100644 index 0000000000..3238a16eb8 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientCommandBefore.netcore.cs @@ -0,0 +1,81 @@ +// 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. + +#if NET + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + /// + public sealed class SqlClientCommandBefore : IReadOnlyList> + { + /// + public const string Name = "Microsoft.Data.SqlClient.WriteCommandBefore"; + + internal SqlClientCommandBefore( + Guid operationId, + string operation, + long timestamp, + Guid? connectionId, + long? transactionId, + SqlCommand command) + { + OperationId = operationId; + Operation = operation; + Timestamp = timestamp; + ConnectionId = connectionId; + TransactionId = transactionId; + Command = command; + } + + /// + public Guid OperationId { get; } + /// + public string Operation { get; } + /// + public long Timestamp { get; } + /// + public Guid? ConnectionId { get; } + /// + public long? TransactionId { get; } + /// + public SqlCommand Command { get; } + + /// + public int Count => 3 + 3; + + /// + public KeyValuePair this[int index] + { + get => index switch + { + 0 => new KeyValuePair(nameof(OperationId), OperationId), + 1 => new KeyValuePair(nameof(Operation), Operation), + 2 => new KeyValuePair(nameof(Timestamp), Timestamp), + 3 => new KeyValuePair(nameof(ConnectionId), ConnectionId), + 4 => new KeyValuePair(nameof(TransactionId), TransactionId), + 5 => new KeyValuePair(nameof(Command), Command), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public IEnumerator> GetEnumerator() + { + int count = Count; + for (int index = 0; index < count; index++) + { + yield return this[index]; + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientCommandError.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientCommandError.netcore.cs new file mode 100644 index 0000000000..ea383305d0 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientCommandError.netcore.cs @@ -0,0 +1,86 @@ +// 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. + +#if NET + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + /// + public sealed class SqlClientCommandError : IReadOnlyList> + { + /// + + public const string Name = "Microsoft.Data.SqlClient.WriteCommandError"; + + internal SqlClientCommandError( + Guid operationId, + string operation, + long timestamp, + Guid? connectionId, + long? transactionId, + SqlCommand command, + Exception exception) + { + OperationId = operationId; + Operation = operation; + Timestamp = timestamp; + ConnectionId = connectionId; + TransactionId = transactionId; + Command = command; + Exception = exception; + } + /// + public Guid OperationId { get; } + /// + public string Operation { get; } + /// + public long Timestamp { get; } + /// + public Guid? ConnectionId { get; } + /// + public long? TransactionId { get; } + /// + public SqlCommand Command { get; } + /// + public Exception Exception { get; } + + /// + public int Count => 3 + 4; + + /// + public KeyValuePair this[int index] + { + get => index switch + { + 0 => new KeyValuePair(nameof(OperationId), OperationId), + 1 => new KeyValuePair(nameof(Operation), Operation), + 2 => new KeyValuePair(nameof(Timestamp), Timestamp), + 3 => new KeyValuePair(nameof(ConnectionId), ConnectionId), + 4 => new KeyValuePair(nameof(TransactionId), TransactionId), + 5 => new KeyValuePair(nameof(Command), Command), + 6 => new KeyValuePair(nameof(Exception), Exception), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public IEnumerator> GetEnumerator() + { + int count = Count; + for (int index = 0; index < count; index++) + { + yield return this[index]; + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionCloseAfter.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionCloseAfter.netcore.cs new file mode 100644 index 0000000000..c3905c2265 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionCloseAfter.netcore.cs @@ -0,0 +1,81 @@ +// 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. + +#if NET + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + /// + public sealed class SqlClientConnectionCloseAfter : IReadOnlyList> + { + /// + public const string Name = "Microsoft.Data.SqlClient.WriteConnectionCloseAfter"; + + internal SqlClientConnectionCloseAfter( + Guid operationId, + string operation, + long timestamp, + Guid? connectionId, + SqlConnection connection, + IDictionary statistics) + { + OperationId = operationId; + Operation = operation; + Timestamp = timestamp; + ConnectionId = connectionId; + Connection = connection; + Statistics = statistics; + } + + /// + public Guid OperationId { get; } + /// + public string Operation { get; } + /// + public long Timestamp { get; } + /// + public Guid? ConnectionId { get; } + /// + public SqlConnection Connection { get; } + /// + public IDictionary Statistics { get; } + + /// + public int Count => 3 + 3; + + /// + public KeyValuePair this[int index] + { + get => index switch + { + 0 => new KeyValuePair(nameof(OperationId), OperationId), + 1 => new KeyValuePair(nameof(Operation), Operation), + 2 => new KeyValuePair(nameof(Timestamp), Timestamp), + 3 => new KeyValuePair(nameof(ConnectionId), ConnectionId), + 4 => new KeyValuePair(nameof(Connection), Connection), + 5 => new KeyValuePair(nameof(Statistics), Statistics), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public IEnumerator> GetEnumerator() + { + int count = Count; + for (int index = 0; index < count; index++) + { + yield return this[index]; + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionCloseBefore.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionCloseBefore.netcore.cs new file mode 100644 index 0000000000..ab08c9d5c5 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionCloseBefore.netcore.cs @@ -0,0 +1,81 @@ +// 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. + +#if NET + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + /// + public sealed class SqlClientConnectionCloseBefore : IReadOnlyList> + { + /// + public const string Name = "Microsoft.Data.SqlClient.WriteConnectionCloseBefore"; + + internal SqlClientConnectionCloseBefore( + Guid operationId, + string operation, + long timestamp, + Guid? connectionId, + SqlConnection connection, + IDictionary statistics) + { + OperationId = operationId; + Operation = operation; + Timestamp = timestamp; + ConnectionId = connectionId; + Connection = connection; + Statistics = statistics; + } + + /// + public Guid OperationId { get; } + /// + public string Operation { get; } + /// + public long Timestamp { get; } + /// + public Guid? ConnectionId { get; } + /// + public SqlConnection Connection { get; } + /// + public IDictionary Statistics { get; } + + /// + public int Count => 3 + 3; + + /// + public KeyValuePair this[int index] + { + get => index switch + { + 0 => new KeyValuePair(nameof(OperationId), OperationId), + 1 => new KeyValuePair(nameof(Operation), Operation), + 2 => new KeyValuePair(nameof(Timestamp), Timestamp), + 3 => new KeyValuePair(nameof(ConnectionId), ConnectionId), + 4 => new KeyValuePair(nameof(Connection), Connection), + 5 => new KeyValuePair(nameof(Statistics), Statistics), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public IEnumerator> GetEnumerator() + { + int count = Count; + for (int index = 0; index < count; index++) + { + yield return this[index]; + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionCloseError.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionCloseError.netcore.cs new file mode 100644 index 0000000000..8b9e51bacf --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionCloseError.netcore.cs @@ -0,0 +1,88 @@ +// 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. + +#if NET + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + /// + public sealed class SqlClientConnectionCloseError : IReadOnlyList> + { + /// + public const string Name = "Microsoft.Data.SqlClient.WriteConnectionCloseError"; + + internal SqlClientConnectionCloseError( + Guid operationId, + string operation, + long timestamp, + Guid? connectionId, + SqlConnection connection, + IDictionary statistics, + Exception ex) + { + OperationId = operationId; + Operation = operation; + Timestamp = timestamp; + ConnectionId = connectionId; + Connection = connection; + Statistics = statistics; + Exception = ex; + } + + /// + public Guid OperationId { get; } + /// + public string Operation { get; } + /// + public long Timestamp { get; } + + /// + public Guid? ConnectionId { get; } + /// + public SqlConnection Connection { get; } + /// + public IDictionary Statistics { get; } + + /// + public Exception Exception { get; } + + /// + public int Count => 3 + 4; + + /// + public KeyValuePair this[int index] + { + get => index switch + { + 0 => new KeyValuePair(nameof(OperationId), OperationId), + 1 => new KeyValuePair(nameof(Operation), Operation), + 2 => new KeyValuePair(nameof(Timestamp), Timestamp), + 3 => new KeyValuePair(nameof(ConnectionId), ConnectionId), + 4 => new KeyValuePair(nameof(Connection), Connection), + 5 => new KeyValuePair(nameof(Statistics), Statistics), + 6 => new KeyValuePair(nameof(Exception), Exception), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public IEnumerator> GetEnumerator() + { + int count = Count; + for (int index = 0; index < count; index++) + { + yield return this[index]; + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionOpenAfter.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionOpenAfter.netcore.cs new file mode 100644 index 0000000000..b1deaec902 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionOpenAfter.netcore.cs @@ -0,0 +1,86 @@ +// 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. + +#if NET + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + /// + public sealed class SqlClientConnectionOpenAfter : IReadOnlyList> + { + /// + public const string Name = "Microsoft.Data.SqlClient.WriteConnectionOpenAfter"; + + internal SqlClientConnectionOpenAfter( + Guid operationId, + string operation, + long timestamp, + Guid connectionId, + SqlConnection connection, + string clientVersion, + IDictionary statistics) + { + OperationId = operationId; + Operation = operation; + Timestamp = timestamp; + ConnectionId = connectionId; + Connection = connection; + ClientVersion = clientVersion; + Statistics = statistics; + } + + /// + public Guid OperationId { get; } + /// + public string Operation { get; } + /// + public long Timestamp { get; } + /// + public Guid ConnectionId { get; } + /// + public SqlConnection Connection { get; } + /// + public string ClientVersion { get; } + /// + public IDictionary Statistics { get; } + + /// + public int Count => 3 + 4; + + /// + public KeyValuePair this[int index] + { + get => index switch + { + 0 => new KeyValuePair(nameof(OperationId), OperationId), + 1 => new KeyValuePair(nameof(Operation), Operation), + 2 => new KeyValuePair(nameof(Timestamp), Timestamp), + 3 => new KeyValuePair(nameof(ConnectionId), ConnectionId), + 4 => new KeyValuePair(nameof(Connection), Connection), + 5 => new KeyValuePair(nameof(ClientVersion), ClientVersion), + 6 => new KeyValuePair(nameof(Statistics), Statistics), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public IEnumerator> GetEnumerator() + { + int count = Count; + for (int index = 0; index < count; index++) + { + yield return this[index]; + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionOpenBefore.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionOpenBefore.netcore.cs new file mode 100644 index 0000000000..fff83cd3de --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionOpenBefore.netcore.cs @@ -0,0 +1,76 @@ +// 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. + +#if NET + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + /// + public sealed class SqlClientConnectionOpenBefore : IReadOnlyList> + { + /// + public const string Name = "Microsoft.Data.SqlClient.WriteConnectionOpenBefore"; + + internal SqlClientConnectionOpenBefore( + Guid operationId, + string operation, + long timestamp, + SqlConnection connection, + string clientVersion) + { + OperationId = operationId; + Operation = operation; + Timestamp = timestamp; + Connection = connection; + ClientVersion = clientVersion; + } + + /// + public Guid OperationId { get; } + /// + public string Operation { get; } + /// + public long Timestamp { get; } + /// + public SqlConnection Connection { get; } + /// + public string ClientVersion { get; } + + /// + public int Count => 3 + 2; + + /// + public KeyValuePair this[int index] + { + get => index switch + { + 0 => new KeyValuePair(nameof(OperationId), OperationId), + 1 => new KeyValuePair(nameof(Operation), Operation), + 2 => new KeyValuePair(nameof(Timestamp), Timestamp), + 3 => new KeyValuePair(nameof(Connection), Connection), + 4 => new KeyValuePair(nameof(ClientVersion), ClientVersion), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public IEnumerator> GetEnumerator() + { + int count = Count; + for (int index = 0; index < count; index++) + { + yield return this[index]; + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionOpenError.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionOpenError.netcore.cs new file mode 100644 index 0000000000..9823eac1d1 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientConnectionOpenError.netcore.cs @@ -0,0 +1,88 @@ +// 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. + +#if NET + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + /// + public sealed class SqlClientConnectionOpenError : IReadOnlyList> + { + /// + + public const string Name = "Microsoft.Data.SqlClient.WriteConnectionOpenError"; + + internal SqlClientConnectionOpenError( + Guid operationId, + string operation, + long timestamp, + Guid connectionId, + SqlConnection connection, + string clientVersion, + Exception exception) + { + OperationId = operationId; + Operation = operation; + Timestamp = timestamp; + ConnectionId = connectionId; + Connection = connection; + ClientVersion = clientVersion; + Exception = exception; + } + + /// + public Guid OperationId { get; } + /// + public string Operation { get; } + /// + public long Timestamp { get; } + + /// + public Guid ConnectionId { get; } + /// + public SqlConnection Connection { get; } + /// + public string ClientVersion { get; } + /// + public Exception Exception { get; } + + /// + public int Count => 3 + 4; + + /// + public KeyValuePair this[int index] + { + get => index switch + { + 0 => new KeyValuePair(nameof(OperationId), OperationId), + 1 => new KeyValuePair(nameof(Operation), Operation), + 2 => new KeyValuePair(nameof(Timestamp), Timestamp), + 3 => new KeyValuePair(nameof(ConnectionId), ConnectionId), + 4 => new KeyValuePair(nameof(Connection), Connection), + 5 => new KeyValuePair(nameof(ClientVersion), ClientVersion), + 6 => new KeyValuePair(nameof(Exception), Exception), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public IEnumerator> GetEnumerator() + { + int count = Count; + for (int index = 0; index < count; index++) + { + yield return this[index]; + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs new file mode 100644 index 0000000000..f62c65b122 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientMetrics.cs @@ -0,0 +1,570 @@ +// 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.Tracing; + +#if NET +using System.Threading; +#else +using Interop.Windows.Kernel32; +using Microsoft.Data.Common; + +using System.Diagnostics; +using System.Reflection; +using System.Runtime.ConstrainedExecution; +using System.Runtime.Versioning; +using System.Security.Permissions; +#endif + +#nullable enable + +namespace Microsoft.Data.SqlClient.Diagnostics +{ +#if NETFRAMEWORK + [PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")] +#endif + internal sealed partial class SqlClientMetrics + { +#if NETFRAMEWORK + private const string PerformanceCounterCategoryName = ".NET Data Provider for SqlServer"; + private const string PerformanceCounterCategoryHelp = "Counters for Microsoft.Data.SqlClient"; + private const int CounterInstanceNameMaxLength = 127; +#endif + + private readonly SqlClientEventSource _eventSource; + // The names of the below variables must match between .NET Framework and .NET Core. +#if NET + private PollingCounter? _activeHardConnectionsCounter; + private long _activeHardConnections = 0; + private IncrementingPollingCounter? _hardConnectsPerSecondCounter; + private long _hardConnectsRate = 0; + private IncrementingPollingCounter? _hardDisconnectsPerSecondCounter; + private long _hardDisconnectsRate = 0; + + private PollingCounter? _activeSoftConnectionsCounter; + private long _activeSoftConnections = 0; + private IncrementingPollingCounter? _softConnectsPerSecondCounter; + private long _softConnectsRate = 0; + private IncrementingPollingCounter? _softDisconnectsPerSecondCounter; + private long _softDisconnectsRate = 0; + + private PollingCounter? _numberOfNonPooledConnectionsCounter; + private long _nonPooledConnections = 0; + private PollingCounter? _numberOfPooledConnectionsCounter; + private long _pooledConnections = 0; + + private PollingCounter? _numberOfActiveConnectionPoolGroupsCounter; + private long _activeConnectionPoolGroups = 0; + private PollingCounter? _numberOfInactiveConnectionPoolGroupsCounter; + private long _inactiveConnectionPoolGroups = 0; + + private PollingCounter? _numberOfActiveConnectionPoolsCounter; + private long _activeConnectionPools = 0; + private PollingCounter? _numberOfInactiveConnectionPoolsCounter; + private long _inactiveConnectionPools = 0; + + private PollingCounter? _numberOfActiveConnectionsCounter; + private long _activeConnections = 0; + private PollingCounter? _numberOfFreeConnectionsCounter; + private long _freeConnections = 0; + private PollingCounter? _numberOfStasisConnectionsCounter; + private long _stasisConnections = 0; + private IncrementingPollingCounter? _numberOfReclaimedConnectionsCounter; + private long _reclaimedConnections = 0; +#else + private PerformanceCounter? _hardConnectsRate; + private PerformanceCounter? _hardDisconnectsRate; + private PerformanceCounter? _softConnectsRate; + private PerformanceCounter? _softDisconnectsRate; + private PerformanceCounter? _nonPooledConnections; + private PerformanceCounter? _pooledConnections; + private PerformanceCounter? _activeConnectionPoolGroups; + private PerformanceCounter? _inactiveConnectionPoolGroups; + private PerformanceCounter? _activeConnectionPools; + private PerformanceCounter? _inactiveConnectionPools; + private PerformanceCounter? _activeConnections; + private PerformanceCounter? _freeConnections; + private PerformanceCounter? _stasisConnections; + private PerformanceCounter? _reclaimedConnections; + + private string? _instanceName; +#endif + + public SqlClientMetrics(SqlClientEventSource eventSource) + { + _eventSource = eventSource; + +#if NETFRAMEWORK + // On .NET Framework, metrics are exposed as performance counters and are always enabled. + // On .NET Core, metrics are exposed as EventCounters, and require explicit enablement. + EnablePerformanceCounters(); +#endif + } + +#if NET + private static void IncrementPlatformSpecificCounter(ref long counter) + => Interlocked.Increment(ref counter); + + private static void DecrementPlatformSpecificCounter(ref long counter) + => Interlocked.Decrement(ref counter); +#else + // .NET Framework doesn't strictly require the PerformanceCounter parameter to be passed as a ref, but doing + // so means that IncrementPlatformSpecificCounter and DecrementPlatformSpecificCounter can be called in identical + // ways between .NET Framework and .NET Core. + private static void IncrementPlatformSpecificCounter(ref PerformanceCounter? counter) + => counter?.Increment(); + + private static void DecrementPlatformSpecificCounter(ref PerformanceCounter? counter) + => counter?.Decrement(); +#endif + + /// + /// The number of actual connections that are being made to servers + /// + internal void HardConnectRequest() + { +#if NET + IncrementPlatformSpecificCounter(ref _activeHardConnections); +#endif + IncrementPlatformSpecificCounter(ref _hardConnectsRate); + } + + /// + /// The number of actual disconnects that are being made to servers + /// + internal void HardDisconnectRequest() + { +#if NET + DecrementPlatformSpecificCounter(ref _activeHardConnections); +#endif + IncrementPlatformSpecificCounter(ref _hardDisconnectsRate); + } + + /// + /// The number of connections we get from the pool + /// + internal void SoftConnectRequest() + { +#if NET + IncrementPlatformSpecificCounter(ref _activeSoftConnections); +#endif + IncrementPlatformSpecificCounter(ref _softConnectsRate); + } + + /// + /// The number of connections we return to the pool + /// + internal void SoftDisconnectRequest() + { +#if NET + DecrementPlatformSpecificCounter(ref _activeSoftConnections); +#endif + IncrementPlatformSpecificCounter(ref _softDisconnectsRate); + } + + /// + /// The number of connections that are not using connection pooling + /// + internal void EnterNonPooledConnection() + { + IncrementPlatformSpecificCounter(ref _nonPooledConnections); + } + + /// + /// The number of connections that are not using connection pooling + /// + internal void ExitNonPooledConnection() + { + DecrementPlatformSpecificCounter(ref _nonPooledConnections); + } + + /// + /// The number of connections that are managed by the connection pool + /// + internal void EnterPooledConnection() + { + IncrementPlatformSpecificCounter(ref _pooledConnections); + } + + /// + /// The number of connections that are managed by the connection pool + /// + internal void ExitPooledConnection() + { + DecrementPlatformSpecificCounter(ref _pooledConnections); + } + + /// + /// The number of unique connection strings + /// + internal void EnterActiveConnectionPoolGroup() + { + IncrementPlatformSpecificCounter(ref _activeConnectionPoolGroups); + } + + /// + /// The number of unique connection strings + /// + internal void ExitActiveConnectionPoolGroup() + { + DecrementPlatformSpecificCounter(ref _activeConnectionPoolGroups); + } + + /// + /// The number of unique connection strings waiting for pruning + /// + internal void EnterInactiveConnectionPoolGroup() + { + IncrementPlatformSpecificCounter(ref _inactiveConnectionPoolGroups); + } + + /// + /// The number of unique connection strings waiting for pruning + /// + internal void ExitInactiveConnectionPoolGroup() + { + DecrementPlatformSpecificCounter(ref _inactiveConnectionPoolGroups); + } + + /// + /// The number of connection pools + /// + internal void EnterActiveConnectionPool() + { + IncrementPlatformSpecificCounter(ref _activeConnectionPools); + } + + /// + /// The number of connection pools + /// + internal void ExitActiveConnectionPool() + { + DecrementPlatformSpecificCounter(ref _activeConnectionPools); + } + + /// + /// The number of connection pools + /// + internal void EnterInactiveConnectionPool() + { + IncrementPlatformSpecificCounter(ref _inactiveConnectionPools); + } + + /// + /// The number of connection pools + /// + internal void ExitInactiveConnectionPool() + { + DecrementPlatformSpecificCounter(ref _inactiveConnectionPools); + } + + /// + /// The number of connections currently in-use + /// + internal void EnterActiveConnection() + { + IncrementPlatformSpecificCounter(ref _activeConnections); + } + + /// + /// The number of connections currently in-use + /// + internal void ExitActiveConnection() + { + DecrementPlatformSpecificCounter(ref _activeConnections); + } + + /// + /// The number of connections currently available for use + /// + internal void EnterFreeConnection() + { + IncrementPlatformSpecificCounter(ref _freeConnections); + } + + /// + /// The number of connections currently available for use + /// + internal void ExitFreeConnection() + { + DecrementPlatformSpecificCounter(ref _freeConnections); + } + + /// + /// The number of connections currently waiting to be made ready for use + /// + internal void EnterStasisConnection() + { + IncrementPlatformSpecificCounter(ref _stasisConnections); + } + + /// + /// The number of connections currently waiting to be made ready for use + /// + internal void ExitStasisConnection() + { + DecrementPlatformSpecificCounter(ref _stasisConnections); + } + + /// + /// The number of connections we reclaim from GC'd external connections + /// + internal void ReclaimedConnectionRequest() + { + IncrementPlatformSpecificCounter(ref _reclaimedConnections); + } + +#if NET + public void EnableEventCounters() + { + _activeHardConnectionsCounter ??= new PollingCounter("active-hard-connections", _eventSource, () => _activeHardConnections) + { + DisplayName = "Actual active connections currently made to servers", + DisplayUnits = "count" + }; + + _hardConnectsPerSecondCounter ??= new IncrementingPollingCounter("hard-connects", _eventSource, () => _hardConnectsRate) + { + DisplayName = "Actual connection rate to servers", + DisplayUnits = "count / sec", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + + _hardDisconnectsPerSecondCounter ??= new IncrementingPollingCounter("hard-disconnects", _eventSource, () => _hardDisconnectsRate) + { + DisplayName = "Actual disconnection rate from servers", + DisplayUnits = "count / sec", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + + _activeSoftConnectionsCounter ??= new PollingCounter("active-soft-connects", _eventSource, () => _activeSoftConnections) + { + DisplayName = "Active connections retrieved from the connection pool", + DisplayUnits = "count" + }; + + _softConnectsPerSecondCounter ??= new IncrementingPollingCounter("soft-connects", _eventSource, () => _softConnectsRate) + { + DisplayName = "Rate of connections retrieved from the connection pool", + DisplayUnits = "count / sec", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + + _softDisconnectsPerSecondCounter ??= new IncrementingPollingCounter("soft-disconnects", _eventSource, () => _softDisconnectsRate) + { + DisplayName = "Rate of connections returned to the connection pool", + DisplayUnits = "count / sec", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + + _numberOfNonPooledConnectionsCounter ??= new PollingCounter("number-of-non-pooled-connections", _eventSource, () => _nonPooledConnections) + { + DisplayName = "Number of connections not using connection pooling", + DisplayUnits = "count" + }; + + _numberOfPooledConnectionsCounter ??= new PollingCounter("number-of-pooled-connections", _eventSource, () => _pooledConnections) + { + DisplayName = "Number of connections managed by the connection pool", + DisplayUnits = "count" + }; + + _numberOfActiveConnectionPoolGroupsCounter ??= new PollingCounter("number-of-active-connection-pool-groups", _eventSource, () => _activeConnectionPoolGroups) + { + DisplayName = "Number of active unique connection strings", + DisplayUnits = "count" + }; + + _numberOfInactiveConnectionPoolGroupsCounter ??= new PollingCounter("number-of-inactive-connection-pool-groups", _eventSource, () => _inactiveConnectionPoolGroups) + { + DisplayName = "Number of unique connection strings waiting for pruning", + DisplayUnits = "count" + }; + + _numberOfActiveConnectionPoolsCounter ??= new PollingCounter("number-of-active-connection-pools", _eventSource, () => _activeConnectionPools) + { + DisplayName = "Number of active connection pools", + DisplayUnits = "count" + }; + + _numberOfInactiveConnectionPoolsCounter ??= new PollingCounter("number-of-inactive-connection-pools", _eventSource, () => _inactiveConnectionPools) + { + DisplayName = "Number of inactive connection pools", + DisplayUnits = "count" + }; + + _numberOfActiveConnectionsCounter ??= new PollingCounter("number-of-active-connections", _eventSource, () => _activeConnections) + { + DisplayName = "Number of active connections", + DisplayUnits = "count" + }; + + _numberOfFreeConnectionsCounter ??= new PollingCounter("number-of-free-connections", _eventSource, () => _freeConnections) + { + DisplayName = "Number of ready connections in the connection pool", + DisplayUnits = "count" + }; + + _numberOfStasisConnectionsCounter ??= new PollingCounter("number-of-stasis-connections", _eventSource, () => _stasisConnections) + { + DisplayName = "Number of connections currently waiting to be ready", + DisplayUnits = "count" + }; + + _numberOfReclaimedConnectionsCounter ??= new IncrementingPollingCounter("number-of-reclaimed-connections", _eventSource, () => _reclaimedConnections) + { + DisplayName = "Number of reclaimed connections from GC", + DisplayUnits = "count", + DisplayRateTimeScale = TimeSpan.FromSeconds(1) + }; + } +#else + [PerformanceCounterPermission(SecurityAction.Assert, PermissionAccess = PerformanceCounterPermissionAccess.Write, + MachineName = ".", CategoryName = PerformanceCounterCategoryName)] + private void EnablePerformanceCounters() + { + AppDomain.CurrentDomain.DomainUnload += ExitOrUnloadEventHandler; + AppDomain.CurrentDomain.ProcessExit += ExitOrUnloadEventHandler; + AppDomain.CurrentDomain.UnhandledException += ExceptionEventHandler; + + // level 0-3: hard connects/disconnects, plus basic pool/pool entry statistics + _hardConnectsRate = CreatePerformanceCounter("HardConnectsPerSecond", PerformanceCounterType.RateOfCountsPerSecond32); + _hardDisconnectsRate = CreatePerformanceCounter("HardDisconnectsPerSecond", PerformanceCounterType.RateOfCountsPerSecond32); + _nonPooledConnections = CreatePerformanceCounter("NumberOfNonPooledConnections", PerformanceCounterType.NumberOfItems32); + _pooledConnections = CreatePerformanceCounter("NumberOfPooledConnections", PerformanceCounterType.NumberOfItems32); + _activeConnectionPoolGroups = CreatePerformanceCounter("NumberOfActiveConnectionPoolGroups", PerformanceCounterType.NumberOfItems32); + _inactiveConnectionPoolGroups = CreatePerformanceCounter("NumberOfInactiveConnectionPoolGroups", PerformanceCounterType.NumberOfItems32); + _activeConnectionPools = CreatePerformanceCounter("NumberOfActiveConnectionPools", PerformanceCounterType.NumberOfItems32); + _inactiveConnectionPools = CreatePerformanceCounter("NumberOfInactiveConnectionPools", PerformanceCounterType.NumberOfItems32); + _stasisConnections = CreatePerformanceCounter("NumberOfStasisConnections", PerformanceCounterType.NumberOfItems32); + _reclaimedConnections = CreatePerformanceCounter("NumberOfReclaimedConnections", PerformanceCounterType.NumberOfItems32); + + TraceSwitch perfCtrSwitch = new TraceSwitch("ConnectionPoolPerformanceCounterDetail", "level of detail to track with connection pool performance counters"); + if (TraceLevel.Verbose == perfCtrSwitch.Level) + { + _softConnectsRate = CreatePerformanceCounter("SoftConnectsPerSecond", PerformanceCounterType.RateOfCountsPerSecond32); + _softDisconnectsRate = CreatePerformanceCounter("SoftDisconnectsPerSecond", PerformanceCounterType.RateOfCountsPerSecond32); + _activeConnections = CreatePerformanceCounter("NumberOfActiveConnections", PerformanceCounterType.NumberOfItems32); + _freeConnections = CreatePerformanceCounter("NumberOfFreeConnections", PerformanceCounterType.NumberOfItems32); + } + } + + [PrePrepareMethod] + private void ExitOrUnloadEventHandler(object sender, EventArgs e) + { + RemovePerformanceCounters(); + } + + [PrePrepareMethod] + private void ExceptionEventHandler(object sender, UnhandledExceptionEventArgs e) + { + if (e != null && e.IsTerminating) + { + RemovePerformanceCounters(); + } + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] + private void RemovePerformanceCounters() + { + // ExceptionEventHandler with IsTerminating may be called before + // the Connection Close is called or the variables are initialized + _hardConnectsRate?.RemoveInstance(); + _hardDisconnectsRate?.RemoveInstance(); + _softConnectsRate?.RemoveInstance(); + _softDisconnectsRate?.RemoveInstance(); + _nonPooledConnections?.RemoveInstance(); + _pooledConnections?.RemoveInstance(); + _activeConnectionPoolGroups?.RemoveInstance(); + _inactiveConnectionPoolGroups?.RemoveInstance(); + _activeConnectionPools?.RemoveInstance(); + _inactiveConnectionPools?.RemoveInstance(); + _activeConnections?.RemoveInstance(); + _freeConnections?.RemoveInstance(); + _stasisConnections?.RemoveInstance(); + _reclaimedConnections?.RemoveInstance(); + } + + private PerformanceCounter? CreatePerformanceCounter(string counterName, PerformanceCounterType counterType) + { + PerformanceCounter? instance = null; + + _instanceName ??= GetInstanceName(); + try + { + instance = new PerformanceCounter(); + instance.CategoryName = PerformanceCounterCategoryName; + instance.CounterName = counterName; + instance.InstanceName = _instanceName; + instance.InstanceLifetime = PerformanceCounterInstanceLifetime.Process; + instance.ReadOnly = false; + instance.RawValue = 0; // make sure we start out at zero + } + catch (InvalidOperationException e) + { + ADP.TraceExceptionWithoutRethrow(e); + } + + return instance; + } + + // SxS: this method uses GetCurrentProcessId to construct the instance name. + // TODO: VSDD 534795 - remove the Resource* attributes if you do not use GetCurrentProcessId after the fix + [ResourceExposure(ResourceScope.None)] + [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] + private static string GetInstanceName() + { + string result; + string? instanceName = GetAssemblyName(); // instance perfcounter name + + if (string.IsNullOrEmpty(instanceName)) + { + instanceName = AppDomain.CurrentDomain?.FriendlyName; + } + + // TODO: If you do not use GetCurrentProcessId after fixing VSDD 534795, please remove Resource* attributes from this method + int pid = Kernel32Safe.GetCurrentProcessId(); + + // SQLBUDT #366157 -there are several characters which have special meaning + // to PERFMON. They recommend that we translate them as shown below, to + // prevent problems. + + result = string.Format(null, "{0}[{1}]", instanceName, pid); + result = result.Replace('(', '[').Replace(')', ']').Replace('#', '_').Replace('/', '_').Replace('\\', '_'); + + // SQLBUVSTS #94625 - counter instance name cannot be greater than 127 + if (result.Length > CounterInstanceNameMaxLength) + { + // Replacing the middle part with "[...]" + // For example: if path is c:\long_path\very_(Ax200)_long__path\perftest.exe and process ID is 1234 than the resulted instance name will be: + // c:\long_path\very_(AxM)[...](AxN)_long__path\perftest.exe[1234] + // while M and N are adjusted to make each part before and after the [...] = 61 (making the total = 61 + 5 + 61 = 127) + const string insertString = "[...]"; + int firstPartLength = (CounterInstanceNameMaxLength - insertString.Length) / 2; + int lastPartLength = CounterInstanceNameMaxLength - firstPartLength - insertString.Length; + result = string.Format(null, "{0}{1}{2}", + result.Substring(0, firstPartLength), + insertString, + result.Substring(result.Length - lastPartLength, lastPartLength)); + + Debug.Assert(result.Length == CounterInstanceNameMaxLength, + string.Format(null, "wrong calculation of the instance name: expected {0}, actual: {1}", CounterInstanceNameMaxLength, result.Length)); + } + + return result; + } + + [FileIOPermission(SecurityAction.Assert, Unrestricted = true)] + private static string? GetAssemblyName() + { + // First try GetEntryAssembly name, then AppDomain.FriendlyName. + Assembly? assembly = Assembly.GetEntryAssembly(); + AssemblyName? name = assembly?.GetName(); + + return name?.Name; + } +#endif + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionCommitAfter.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionCommitAfter.netcore.cs new file mode 100644 index 0000000000..b4b44ae07c --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionCommitAfter.netcore.cs @@ -0,0 +1,83 @@ +// 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. + +#if NET + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + /// + public sealed class SqlClientTransactionCommitAfter : IReadOnlyList> + { + /// + public const string Name = "Microsoft.Data.SqlClient.WriteTransactionCommitAfter"; + + internal SqlClientTransactionCommitAfter( + Guid operationId, + string operation, + long timestamp, + IsolationLevel isolationLevel, + SqlConnection connection, + long? transactionId) + { + OperationId = operationId; + Operation = operation; + Timestamp = timestamp; + IsolationLevel = isolationLevel; + Connection = connection; + TransactionId = transactionId; + } + + /// + public Guid OperationId { get; } + /// + public string Operation { get; } + /// + public long Timestamp { get; } + + /// + public IsolationLevel IsolationLevel { get; } + /// + public SqlConnection Connection { get; } + /// + public long? TransactionId { get; } + + /// + public int Count => 3 + 3; + + /// + public KeyValuePair this[int index] + { + get => index switch + { + 0 => new KeyValuePair(nameof(OperationId), OperationId), + 1 => new KeyValuePair(nameof(Operation), Operation), + 2 => new KeyValuePair(nameof(Timestamp), Timestamp), + 3 => new KeyValuePair(nameof(IsolationLevel), IsolationLevel), + 4 => new KeyValuePair(nameof(Connection), Connection), + 5 => new KeyValuePair(nameof(TransactionId), TransactionId), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public IEnumerator> GetEnumerator() + { + int count = Count; + for (int index = 0; index < count; index++) + { + yield return this[index]; + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionCommitBefore.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionCommitBefore.netcore.cs new file mode 100644 index 0000000000..8e9a6990eb --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionCommitBefore.netcore.cs @@ -0,0 +1,82 @@ +// 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. + +#if NET + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + /// + public sealed class SqlClientTransactionCommitBefore : IReadOnlyList> + { + /// + public const string Name = "Microsoft.Data.SqlClient.WriteTransactionCommitBefore"; + + internal SqlClientTransactionCommitBefore( + Guid operationId, + string operation, + long timestamp, + IsolationLevel isolationLevel, + SqlConnection connection, + long? transactionId) + { + OperationId = operationId; + Operation = operation; + Timestamp = timestamp; + IsolationLevel = isolationLevel; + Connection = connection; + TransactionId = transactionId; + } + + /// + public Guid OperationId { get; } + /// + public string Operation { get; } + /// + public long Timestamp { get; } + /// + public IsolationLevel IsolationLevel { get; } + /// + public SqlConnection Connection { get; } + /// + public long? TransactionId { get; } + + /// + public int Count => 3 + 3; + + /// + public KeyValuePair this[int index] + { + get => index switch + { + 0 => new KeyValuePair(nameof(OperationId), OperationId), + 1 => new KeyValuePair(nameof(Operation), Operation), + 2 => new KeyValuePair(nameof(Timestamp), Timestamp), + 3 => new KeyValuePair(nameof(IsolationLevel), IsolationLevel), + 4 => new KeyValuePair(nameof(Connection), Connection), + 5 => new KeyValuePair(nameof(TransactionId), TransactionId), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public IEnumerator> GetEnumerator() + { + int count = Count; + for (int index = 0; index < count; index++) + { + yield return this[index]; + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionCommitError.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionCommitError.netcore.cs new file mode 100644 index 0000000000..be3e70b792 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionCommitError.netcore.cs @@ -0,0 +1,88 @@ +// 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. + +#if NET + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + /// + public sealed class SqlClientTransactionCommitError : IReadOnlyList> + { + /// + public const string Name = "Microsoft.Data.SqlClient.WriteTransactionCommitError"; + + internal SqlClientTransactionCommitError( + Guid operationId, + string operation, + long timestamp, + IsolationLevel isolationLevel, + SqlConnection connection, + long? transactionId, + Exception ex) + { + OperationId = operationId; + Operation = operation; + Timestamp = timestamp; + IsolationLevel = isolationLevel; + Connection = connection; + TransactionId = transactionId; + Exception = ex; + } + + /// + public Guid OperationId { get; } + /// + public string Operation { get; } + /// + public long Timestamp { get; } + + /// + public IsolationLevel IsolationLevel { get; } + /// + public SqlConnection Connection { get; } + /// + public long? TransactionId { get; } + /// + public Exception Exception { get; } + + /// + public int Count => 3 + 4; + + /// + public KeyValuePair this[int index] + { + get => index switch + { + 0 => new KeyValuePair(nameof(OperationId), OperationId), + 1 => new KeyValuePair(nameof(Operation), Operation), + 2 => new KeyValuePair(nameof(Timestamp), Timestamp), + 3 => new KeyValuePair(nameof(IsolationLevel), IsolationLevel), + 4 => new KeyValuePair(nameof(Connection), Connection), + 5 => new KeyValuePair(nameof(TransactionId), TransactionId), + 6 => new KeyValuePair(nameof(Exception), Exception), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public IEnumerator> GetEnumerator() + { + int count = Count; + for (int index = 0; index < count; index++) + { + yield return this[index]; + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionRollbackAfter.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionRollbackAfter.netcore.cs new file mode 100644 index 0000000000..99a31040f0 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionRollbackAfter.netcore.cs @@ -0,0 +1,87 @@ +// 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. + +#if NET + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + /// + public sealed class SqlClientTransactionRollbackAfter : IReadOnlyList> + { + /// + public const string Name = "Microsoft.Data.SqlClient.WriteTransactionRollbackAfter"; + + internal SqlClientTransactionRollbackAfter( + Guid operationId, + string operation, + long timestamp, + IsolationLevel isolationLevel, + SqlConnection connection, + long? transactionId, + string transactionName) + { + OperationId = operationId; + Operation = operation; + Timestamp = timestamp; + IsolationLevel = isolationLevel; + Connection = connection; + TransactionId = transactionId; + TransactionName = transactionName; + } + + /// + public Guid OperationId { get; } + /// + public string Operation { get; } + /// + public long Timestamp { get; } + /// + public IsolationLevel IsolationLevel { get; } + /// + public SqlConnection Connection { get; } + /// + public long? TransactionId { get; } + /// + public string TransactionName { get; } + + /// + public int Count => 3 + 4; + + /// + public KeyValuePair this[int index] + { + get => index switch + { + 0 => new KeyValuePair(nameof(OperationId), OperationId), + 1 => new KeyValuePair(nameof(Operation), Operation), + 2 => new KeyValuePair(nameof(Timestamp), Timestamp), + 3 => new KeyValuePair(nameof(IsolationLevel), IsolationLevel), + 4 => new KeyValuePair(nameof(Connection), Connection), + 5 => new KeyValuePair(nameof(TransactionId), TransactionId), + 6 => new KeyValuePair(nameof(TransactionName), TransactionName), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public IEnumerator> GetEnumerator() + { + int count = Count; + for (int index = 0; index < count; index++) + { + yield return this[index]; + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionRollbackBefore.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionRollbackBefore.netcore.cs new file mode 100644 index 0000000000..f55f4fcd6d --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionRollbackBefore.netcore.cs @@ -0,0 +1,88 @@ +// 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. + +#if NET + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + /// + public sealed class SqlClientTransactionRollbackBefore : IReadOnlyList> + { + /// + public const string Name = "Microsoft.Data.SqlClient.WriteTransactionRollbackBefore"; + + internal SqlClientTransactionRollbackBefore( + Guid operationId, + string operation, + long timestamp, + IsolationLevel isolationLevel, + SqlConnection connection, + long? transactionId, + string transactionName) + { + OperationId = operationId; + Operation = operation; + Timestamp = timestamp; + IsolationLevel = isolationLevel; + Connection = connection; + TransactionId = transactionId; + TransactionName = transactionName; + } + + /// + public Guid OperationId { get; } + /// + public string Operation { get; } + /// + public long Timestamp { get; } + + /// + public IsolationLevel IsolationLevel { get; } + /// + public SqlConnection Connection { get; } + /// + public long? TransactionId { get; } + /// + public string TransactionName { get; } + + /// + public int Count => 3 + 4; + + /// + public KeyValuePair this[int index] + { + get => index switch + { + 0 => new KeyValuePair(nameof(OperationId), OperationId), + 1 => new KeyValuePair(nameof(Operation), Operation), + 2 => new KeyValuePair(nameof(Timestamp), Timestamp), + 3 => new KeyValuePair(nameof(IsolationLevel), IsolationLevel), + 4 => new KeyValuePair(nameof(Connection), Connection), + 5 => new KeyValuePair(nameof(TransactionId), TransactionId), + 6 => new KeyValuePair(nameof(TransactionName), TransactionName), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public IEnumerator> GetEnumerator() + { + int count = Count; + for (int index = 0; index < count; index++) + { + yield return this[index]; + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionRollbackError.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionRollbackError.netcore.cs new file mode 100644 index 0000000000..9c2e16ba0b --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlClientTransactionRollbackError.netcore.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if NET + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + /// + public sealed class SqlClientTransactionRollbackError : IReadOnlyList> + { + /// + public const string Name = "Microsoft.Data.SqlClient.WriteTransactionRollbackError"; + + internal SqlClientTransactionRollbackError( + Guid operationId, + string operation, + long timestamp, + IsolationLevel isolationLevel, + SqlConnection connection, + long? transactionId, + string transactionName, + Exception ex) + { + OperationId = operationId; + Operation = operation; + Timestamp = timestamp; + IsolationLevel = isolationLevel; + Connection = connection; + TransactionId = transactionId; + TransactionName = transactionName; + Exception = ex; + } + + + /// + public Guid OperationId { get; } + /// + public string Operation { get; } + /// + public long Timestamp { get; } + /// + public IsolationLevel IsolationLevel { get; } + /// + public SqlConnection Connection { get; } + /// + public long? TransactionId { get; } + /// + public string TransactionName { get; } + /// + public Exception Exception { get; } + + /// + public int Count => 3 + 5; + + /// + public KeyValuePair this[int index] + { + get => index switch + { + 0 => new KeyValuePair(nameof(OperationId), OperationId), + 1 => new KeyValuePair(nameof(Operation), Operation), + 2 => new KeyValuePair(nameof(Timestamp), Timestamp), + 3 => new KeyValuePair(nameof(IsolationLevel), IsolationLevel), + 4 => new KeyValuePair(nameof(Connection), Connection), + 5 => new KeyValuePair(nameof(TransactionId), TransactionId), + 6 => new KeyValuePair(nameof(TransactionName), TransactionName), + 7 => new KeyValuePair(nameof(Exception), Exception), + _ => throw new IndexOutOfRangeException(nameof(index)), + }; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public IEnumerator> GetEnumerator() + { + int count = Count; + for (int index = 0; index < count; index++) + { + yield return this[index]; + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlDiagnosticListener.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlDiagnosticListener.netcore.cs new file mode 100644 index 0000000000..2ce73d8988 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Diagnostics/SqlDiagnosticListener.netcore.cs @@ -0,0 +1,487 @@ +// 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. + +#if NET + +using System; +using System.Data; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Loader; + +namespace Microsoft.Data.SqlClient.Diagnostics +{ + internal sealed class SqlDiagnosticListener : DiagnosticListener + { + public SqlDiagnosticListener() : base("SqlClientDiagnosticListener") + { + AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()).Unloading += SqlDiagnosticListener_Unloading; + } + + public DiagnosticScope CreateCommandScope( + SqlCommand command, + SqlTransaction transaction, + [CallerMemberName] + string operationName = "") + { + return DiagnosticScope.CreateCommandScope(this, command, transaction, operationName); + } + + public DiagnosticTransactionScope CreateTransactionCommitScope( + IsolationLevel isolationLevel, + SqlConnection connection, + SqlInternalTransaction transaction, + [CallerMemberName] + string operationName = "") + { + return DiagnosticTransactionScope.CreateTransactionCommitScope( + this, + isolationLevel, + connection, + transaction, + operationName); + } + + public DiagnosticTransactionScope CreateTransactionRollbackScope( + IsolationLevel isolationLevel, + SqlConnection connection, + SqlInternalTransaction transaction, + string transactionName, + [CallerMemberName] + string operationName = "") + { + return DiagnosticTransactionScope.CreateTransactionRollbackScope( + this, + isolationLevel, + connection, + transaction, + transactionName, + operationName); + } + + public void WriteCommandAfter( + Guid operationId, + SqlCommand sqlCommand, + SqlTransaction transaction, + [CallerMemberName] + string operation = "") + { + if (!IsEnabled(SqlClientCommandAfter.Name)) + { + return; + } + + Write( + SqlClientCommandAfter.Name, + new SqlClientCommandAfter( + operationId, + operation, + Stopwatch.GetTimestamp(), + sqlCommand.Connection?.ClientConnectionId, + transaction?.InternalTransaction?.TransactionId, + sqlCommand, + sqlCommand.Statistics?.GetDictionary() + ) + ); + } + + public Guid WriteCommandBefore( + SqlCommand sqlCommand, + SqlTransaction transaction, + [CallerMemberName] + string operation = "") + { + if (!IsEnabled(SqlClientCommandBefore.Name)) + { + return Guid.Empty; + } + + Guid operationId = Guid.NewGuid(); + + Write( + SqlClientCommandBefore.Name, + new SqlClientCommandBefore( + operationId, + operation, + Stopwatch.GetTimestamp(), + sqlCommand.Connection?.ClientConnectionId, + transaction?.InternalTransaction?.TransactionId, + sqlCommand + ) + ); + + return operationId; + } + + public void WriteCommandError( + Guid operationId, + SqlCommand sqlCommand, + SqlTransaction transaction, + Exception ex, + [CallerMemberName] + string operation = "") + { + if (!IsEnabled(SqlClientCommandError.Name)) + { + return; + } + + Write( + SqlClientCommandError.Name, + new SqlClientCommandError + ( + operationId, + operation, + Stopwatch.GetTimestamp(), + sqlCommand.Connection?.ClientConnectionId, + transaction?.InternalTransaction?.TransactionId, + sqlCommand, + ex + ) + ); + } + + public void WriteConnectionCloseAfter( + Guid operationId, + Guid clientConnectionId, + SqlConnection sqlConnection, + [CallerMemberName] + string operation = "") + { + if (!IsEnabled(SqlClientConnectionCloseAfter.Name)) + { + return; + } + + Write( + SqlClientConnectionCloseAfter.Name, + new SqlClientConnectionCloseAfter + ( + operationId, + operation, + Stopwatch.GetTimestamp(), + clientConnectionId, + sqlConnection, + sqlConnection.Statistics?.GetDictionary() + ) + ); + } + + public Guid WriteConnectionCloseBefore(SqlConnection sqlConnection, [CallerMemberName] string operation = "") + { + if (!IsEnabled(SqlClientConnectionCloseBefore.Name)) + { + return Guid.Empty; + } + + Guid operationId = Guid.NewGuid(); + + Write( + SqlClientConnectionCloseBefore.Name, + new SqlClientConnectionCloseBefore + ( + operationId, + operation, + Stopwatch.GetTimestamp(), + sqlConnection.ClientConnectionId, + sqlConnection, + sqlConnection.Statistics?.GetDictionary() + ) + ); + + return operationId; + } + + public void WriteConnectionCloseError( + Guid operationId, + Guid clientConnectionId, + SqlConnection sqlConnection, + Exception ex, + [CallerMemberName] + string operation = "") + { + if (!IsEnabled(SqlClientConnectionCloseError.Name)) + { + return; + } + + Write( + SqlClientConnectionCloseError.Name, + new SqlClientConnectionCloseError + ( + operationId, + operation, + Stopwatch.GetTimestamp(), + clientConnectionId, + sqlConnection, + sqlConnection.Statistics?.GetDictionary(), + ex + ) + ); + } + + public void WriteConnectionOpenAfter( + Guid operationId, + SqlConnection sqlConnection, + [CallerMemberName] + string operation = "") + { + if (!IsEnabled(SqlClientConnectionOpenAfter.Name)) + { + return; + } + + Write( + SqlClientConnectionOpenAfter.Name, + new SqlClientConnectionOpenAfter + ( + operationId, + operation, + Stopwatch.GetTimestamp(), + sqlConnection.ClientConnectionId, + sqlConnection, + ThisAssembly.InformationalVersion, + sqlConnection.Statistics?.GetDictionary() + ) + ); + } + + public Guid WriteConnectionOpenBefore(SqlConnection sqlConnection, [CallerMemberName] string operation = "") + { + if (!IsEnabled(SqlClientConnectionOpenBefore.Name)) + { + return Guid.Empty; + } + + Guid operationId = Guid.NewGuid(); + + Write( + SqlClientConnectionOpenBefore.Name, + new SqlClientConnectionOpenBefore + ( + operationId, + operation, + Stopwatch.GetTimestamp(), + sqlConnection, + ThisAssembly.InformationalVersion + ) + ); + + return operationId; + } + + public void WriteConnectionOpenError( + Guid operationId, + SqlConnection sqlConnection, + Exception ex, + [CallerMemberName] + string operation = "") + { + if (!IsEnabled(SqlClientConnectionOpenError.Name)) + { + return; + } + + Write( + SqlClientConnectionOpenError.Name, + new SqlClientConnectionOpenError + ( + operationId, + operation, + Stopwatch.GetTimestamp(), + sqlConnection.ClientConnectionId, + sqlConnection, + ThisAssembly.InformationalVersion, + ex + ) + ); + } + + public void WriteTransactionCommitAfter( + Guid operationId, + IsolationLevel isolationLevel, + SqlConnection connection, + SqlInternalTransaction transaction, + [CallerMemberName] + string operation = "") + { + if (!IsEnabled(SqlClientTransactionCommitAfter.Name)) + { + return; + } + + Write( + SqlClientTransactionCommitAfter.Name, + new SqlClientTransactionCommitAfter + ( + operationId, + operation, + Stopwatch.GetTimestamp(), + isolationLevel, + connection, + transaction?.TransactionId + ) + ); + } + + public Guid WriteTransactionCommitBefore( + IsolationLevel isolationLevel, + SqlConnection connection, + SqlInternalTransaction transaction, + [CallerMemberName] + string operation = "") + { + if (!IsEnabled(SqlClientTransactionCommitBefore.Name)) + { + return Guid.Empty; + } + + Guid operationId = Guid.NewGuid(); + + Write( + SqlClientTransactionCommitBefore.Name, + new SqlClientTransactionCommitBefore + ( + operationId, + operation, + Stopwatch.GetTimestamp(), + isolationLevel, + connection, + transaction?.TransactionId + ) + ); + + return operationId; + } + + public void WriteTransactionCommitError( + Guid operationId, + IsolationLevel isolationLevel, + SqlConnection connection, + SqlInternalTransaction transaction, + Exception ex, + [CallerMemberName] + string operation = "") + { + if (!IsEnabled(SqlClientTransactionCommitError.Name)) + { + return; + } + + Write( + SqlClientTransactionCommitError.Name, + new SqlClientTransactionCommitError + ( + operationId, + operation, + Stopwatch.GetTimestamp(), + isolationLevel, + connection, + transaction?.TransactionId, + ex + ) + ); + } + + public void WriteTransactionRollbackAfter( + Guid operationId, + IsolationLevel isolationLevel, + SqlConnection connection, + SqlInternalTransaction transaction, + string transactionName = null, + [CallerMemberName] + string operation = "") + { + if (!IsEnabled(SqlClientTransactionRollbackAfter.Name)) + { + return; + } + + Write( + SqlClientTransactionRollbackAfter.Name, + new SqlClientTransactionRollbackAfter + ( + operationId, + operation, + Stopwatch.GetTimestamp(), + isolationLevel, + connection, + transaction?.TransactionId, + transactionName + ) + ); + } + + public Guid WriteTransactionRollbackBefore( + IsolationLevel isolationLevel, + SqlConnection connection, + SqlInternalTransaction transaction, + string transactionName = null, + [CallerMemberName] + string operation = "") + { + if (!IsEnabled(SqlClientTransactionRollbackBefore.Name)) + { + return Guid.Empty; + } + + Guid operationId = Guid.NewGuid(); + + Write( + SqlClientTransactionRollbackBefore.Name, + new SqlClientTransactionRollbackBefore + ( + operationId, + operation, + Stopwatch.GetTimestamp(), + isolationLevel, + connection, + transaction?.TransactionId, + transactionName + ) + ); + + return operationId; + } + + public void WriteTransactionRollbackError( + Guid operationId, + IsolationLevel isolationLevel, + SqlConnection connection, + SqlInternalTransaction transaction, + Exception ex, + string transactionName = null, + [CallerMemberName] + string operation = "") + { + if (!IsEnabled(SqlClientTransactionRollbackError.Name)) + { + return; + } + + Write( + SqlClientTransactionRollbackError.Name, + new SqlClientTransactionRollbackError + ( + operationId, + operation, + Stopwatch.GetTimestamp(), + isolationLevel, + connection, + transaction?.TransactionId, + transactionName, + ex + ) + ); + } + + private void SqlDiagnosticListener_Unloading(AssemblyLoadContext obj) + { + Dispose(); + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ISqlVector.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ISqlVector.cs new file mode 100644 index 0000000000..ca9fe35743 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ISqlVector.cs @@ -0,0 +1,35 @@ +namespace Microsoft.Data.SqlClient +{ + /// + /// Internal interface for types that represent a vector of SQL values. + /// + internal interface ISqlVector + { + /// + /// Gets the number of elements in the vector. + /// + int Length { get; } + + /// + /// Gets the type of the elements in vector as a + /// TDS Vector Header DimensionType value. + /// Refer TDS section 2.2.5.5.7.4 + /// + byte ElementType { get; } + + /// + /// Gets the size (in bytes) of a single element. + /// + byte ElementSize { get; } + + /// + /// Gets the raw vector data formatted for TDS payload. + /// + byte[] VectorPayload { get; } + + /// + /// Returns the total size in bytes for sending SqlVector value on TDS. + /// + int Size { get; } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs index 9ea289f5b7..28d3510407 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -17,17 +17,20 @@ private enum Tristate : byte internal const string MakeReadAsyncBlockingString = @"Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking"; internal const string LegacyRowVersionNullString = @"Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior"; - internal const string SuppressInsecureTLSWarningString = @"Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning"; + internal const string SuppressInsecureTlsWarningString = @"Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning"; internal const string UseMinimumLoginTimeoutString = @"Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin"; internal const string LegacyVarTimeZeroScaleBehaviourString = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour"; + internal const string UseConnectionPoolV2String = @"Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2"; // this field is accessed through reflection in tests and should not be renamed or have the type changed without refactoring NullRow related tests private static Tristate s_legacyRowVersionNullBehavior; - private static Tristate s_suppressInsecureTLSWarning; + private static Tristate s_suppressInsecureTlsWarning; private static Tristate s_makeReadAsyncBlocking; private static Tristate s_useMinimumLoginTimeout; // this field is accessed through reflection in Microsoft.Data.SqlClient.Tests.SqlParameterTests and should not be renamed or have the type changed without refactoring related tests private static Tristate s_legacyVarTimeZeroScaleBehaviour; + private static Tristate s_useConnectionPoolV2; + #if NET static LocalAppContextSwitches() @@ -46,8 +49,8 @@ static LocalAppContextSwitches() #endif #if NETFRAMEWORK - internal const string DisableTNIRByDefaultString = @"Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString"; - private static Tristate s_disableTNIRByDefault; + internal const string DisableTnirByDefaultString = @"Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString"; + private static Tristate s_disableTnirByDefault; /// /// Transparent Network IP Resolution (TNIR) is a revision of the existing MultiSubnetFailover feature. @@ -64,22 +67,22 @@ static LocalAppContextSwitches() /// /// This app context switch defaults to 'false'. /// - public static bool DisableTNIRByDefault + public static bool DisableTnirByDefault { get { - if (s_disableTNIRByDefault == Tristate.NotInitialized) + if (s_disableTnirByDefault == Tristate.NotInitialized) { - if (AppContext.TryGetSwitch(DisableTNIRByDefaultString, out bool returnedValue) && returnedValue) + if (AppContext.TryGetSwitch(DisableTnirByDefaultString, out bool returnedValue) && returnedValue) { - s_disableTNIRByDefault = Tristate.True; + s_disableTnirByDefault = Tristate.True; } else { - s_disableTNIRByDefault = Tristate.False; + s_disableTnirByDefault = Tristate.False; } } - return s_disableTNIRByDefault == Tristate.True; + return s_disableTnirByDefault == Tristate.True; } } #endif @@ -89,22 +92,22 @@ public static bool DisableTNIRByDefault /// This warning can be suppressed by enabling this AppContext switch. /// This app context switch defaults to 'false'. /// - public static bool SuppressInsecureTLSWarning + public static bool SuppressInsecureTlsWarning { get { - if (s_suppressInsecureTLSWarning == Tristate.NotInitialized) + if (s_suppressInsecureTlsWarning == Tristate.NotInitialized) { - if (AppContext.TryGetSwitch(SuppressInsecureTLSWarningString, out bool returnedValue) && returnedValue) + if (AppContext.TryGetSwitch(SuppressInsecureTlsWarningString, out bool returnedValue) && returnedValue) { - s_suppressInsecureTLSWarning = Tristate.True; + s_suppressInsecureTlsWarning = Tristate.True; } else { - s_suppressInsecureTLSWarning = Tristate.False; + s_suppressInsecureTlsWarning = Tristate.False; } } - return s_suppressInsecureTLSWarning == Tristate.True; + return s_suppressInsecureTlsWarning == Tristate.True; } } @@ -206,5 +209,29 @@ public static bool LegacyVarTimeZeroScaleBehaviour return s_legacyVarTimeZeroScaleBehaviour == Tristate.True; } } + + /// + /// When set to true, the connection pool will use the new V2 connection pool implementation. + /// When set to false, the connection pool will use the legacy V1 implementation. + /// This app context switch defaults to 'false'. + /// + public static bool UseConnectionPoolV2 + { + get + { + if (s_useConnectionPoolV2 == Tristate.NotInitialized) + { + if (AppContext.TryGetSwitch(UseConnectionPoolV2String, out bool returnedValue) && returnedValue) + { + s_useConnectionPoolV2 = Tristate.True; + } + else + { + s_useConnectionPoolV2 = Tristate.False; + } + } + return s_useConnectionPoolV2 == Tristate.True; + } + } } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbApi.Unix.cs similarity index 60% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbApi.Unix.cs index ff85256c56..e6f7bcbd1e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/LocalDBAPI.Unix.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbApi.Unix.cs @@ -4,11 +4,14 @@ using System; -namespace Microsoft.Data +namespace Microsoft.Data.SqlClient.LocalDb { - internal static partial class LocalDBAPI + internal static class LocalDbApi { - internal static string GetLocalDBMessage(int hrCode) => + internal static string GetLocalDbInstanceNameFromServerName(string serverName) => + null; + + internal static string GetLocalDbMessage(int hrCode) => throw new PlatformNotSupportedException(Strings.LocalDBNotSupported); // LocalDB is not available for Unix and hence it cannot be supported. } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbApi.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbApi.Windows.cs new file mode 100644 index 0000000000..c160af38d3 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbApi.Windows.cs @@ -0,0 +1,435 @@ +// 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.Globalization; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; +using Interop.Windows.Kernel32; +using Interop.Windows.Sni; + +#if NETFRAMEWORK +using System.Collections.Generic; +using System.Configuration; +using System.Runtime.CompilerServices; +using System.Threading; +#endif + +namespace Microsoft.Data.SqlClient.LocalDb +{ + internal static class LocalDbApi + { + // Buffer size for Local DB error message 1K will be enough for all messages + private const int ErrorMessageBufferSize = 1024; + + private const string LocalDbPrefix = @"(localdb)\"; + private const string LocalDbPrefixNamedPipe = @"np:\\.\pipe\LOCALDB#"; + + // Flag for LocalDbFormatMessage that indicates that message can be truncated if it does + // not fit in the buffer. + private const uint LocalDbTruncateErrorMessage = 1; + + #if NETFRAMEWORK + private const string PartialTrustFlagKey = "ALLOW_LOCALDB_IN_PARTIAL_TRUST"; + #endif + + private static readonly object s_dllLock = new object(); + + #if NETFRAMEWORK + private static readonly object s_configLock = new object(); + #endif + + private static LocalDbFormatMessageDelegate s_localDbFormatMessage; + // This is copy of handle that SNI maintains, so we are responsible for freeing it - + // therefore there we are not using SafeHandle + private static IntPtr s_userInstanceDllHandle = IntPtr.Zero; + + #if NETFRAMEWORK + private static Dictionary s_configurableInstances; + private static PermissionSet s_fullTrust; + private static LocalDbCreateInstanceDelegate s_localDbCreateInstance; + private static bool s_partialTrustAllowed; + private static bool s_partialTrustFlagChecked; + #endif + + #if NETFRAMEWORK + [SuppressUnmanagedCodeSecurity] + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate int LocalDbCreateInstanceDelegate( + [MarshalAs(UnmanagedType.LPWStr)] string version, + [MarshalAs(UnmanagedType.LPWStr)] string instance, + uint flags); + #endif + + [SuppressUnmanagedCodeSecurity] + [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private delegate int LocalDbFormatMessageDelegate( + int hrLocalDb, + uint dwFlags, + uint dwLanguageId, + StringBuilder buffer, + ref uint bufferLength); + + #if NETFRAMEWORK + private static LocalDbCreateInstanceDelegate LocalDbCreateInstance + { + get + { + if (s_localDbCreateInstance is null) + { + RuntimeHelpers.PrepareConstrainedRegions(); + + lock (s_dllLock) + { + if (s_localDbCreateInstance is null) + { + IntPtr functionAddr = LoadProcAddress("LocalDBCreateInstance"); + if (functionAddr == IntPtr.Zero) + { + int hResult = Marshal.GetLastWin32Error(); + SqlClientEventSource.Log.TryTraceEvent(" GetProcAddress for LocalDBCreateInstance error 0x{0}", hResult); + throw CreateLocalDbException(errorMessage: StringsHelper.GetString("LocalDB_MethodNotFound")); + } + + s_localDbCreateInstance = (LocalDbCreateInstanceDelegate)Marshal.GetDelegateForFunctionPointer(functionAddr, typeof(LocalDbCreateInstanceDelegate)); + } + } + } + + return s_localDbCreateInstance; + } + } + #endif + + private static LocalDbFormatMessageDelegate LocalDbFormatMessage + { + get + { + if (s_localDbFormatMessage is null) + { + #if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); + #endif + + lock (s_dllLock) + { + if (s_localDbFormatMessage is null) + { + IntPtr functionAddr = LoadProcAddress("LocalDBFormatMessage"); + if (functionAddr == IntPtr.Zero) + { + // SNI checks for LocalDBFormatMessage during DLL loading, so it is practically impossible to get this error. + int hResult = Marshal.GetLastWin32Error(); + SqlClientEventSource.Log.TryTraceEvent("LocalDBAPI.LocalDBFormatMessage> GetProcAddress for LocalDBFormatMessage error 0x{0}", hResult); + throw CreateLocalDbException(errorMessage: Strings.LocalDB_MethodNotFound); + } + + s_localDbFormatMessage = Marshal.GetDelegateForFunctionPointer(functionAddr); + } + } + } + + return s_localDbFormatMessage; + } + } + + private static IntPtr UserInstanceDllHandle + { + get + { + if (s_userInstanceDllHandle == IntPtr.Zero) + { + #if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); + #endif + + lock (s_dllLock) + { + if (s_userInstanceDllHandle == IntPtr.Zero) + { + SniNativeWrapper.SniQueryInfo(QueryType.SNI_QUERY_LOCALDB_HMODULE, ref s_userInstanceDllHandle); + if (s_userInstanceDllHandle != IntPtr.Zero) + { + #if NETFRAMEWORK + SqlClientEventSource.Log.TryTraceEvent(" LocalDB - handle obtained"); + #else + SqlClientEventSource.Log.TryTraceEvent("LocalDBAPI.UserInstanceDLLHandle | LocalDB - handle obtained"); + #endif + } + else + { + SniNativeWrapper.SniGetLastError(out SniError sniError); + throw CreateLocalDbException( + errorMessage: StringsHelper.GetString("LocalDB_FailedGetDLLHandle"), + sniError: sniError.sniError); + } + } + } + } + return s_userInstanceDllHandle; + } + } + + #if NETFRAMEWORK + internal static void AssertLocalDbPermissions() + { + s_partialTrustAllowed = true; + } + #endif + + #if NETFRAMEWORK + internal static void CreateLocalDbInstance(string instance) + { + DemandLocalDbPermissions(); + if (s_configurableInstances is null) + { + // load list of instances from configuration, mark them as not created + RuntimeHelpers.PrepareConstrainedRegions(); + + lock (s_configLock) + { + if (s_configurableInstances is null) + { + Dictionary tempConfigurableInstances = + new Dictionary(StringComparer.OrdinalIgnoreCase); + object section = ConfigurationManager.GetSection("system.data.localdb"); + if (section is not null) + { + // Validate section type + if (section is not LocalDbConfigurationSection configSection) + { + throw CreateLocalDbException(Strings.LocalDB_BadConfigSectionType); + } + + foreach (LocalDbInstanceElement confElement in configSection.LocalDbInstances) + { + Debug.Assert(confElement.Name != null && confElement.Version != null, "Both name and version should not be null"); + tempConfigurableInstances.Add( + confElement.Name.Trim(), + new InstanceInfo(confElement.Version.Trim())); + } + } + else + { + SqlClientEventSource.Log.TryTraceEvent(" No system.data.localdb section found in configuration"); + } + + s_configurableInstances = tempConfigurableInstances; + } + } + } + + if (!s_configurableInstances.TryGetValue(instance, out InstanceInfo instanceInfo)) + { + // Instance name was not in the config + return; + } + + if (instanceInfo.created) + { + // Instance has already been created + return; + } + + Debug.Assert(!instance.Contains("\0"), "Instance name should contain embedded nulls"); + + if (instanceInfo.version.Contains("\0")) + { + throw CreateLocalDbException(errorMessage: Strings.LocalDB_InvalidVersion, instance: instance); + } + + // LocalDBCreateInstance is thread- and cross-process safe method, it is OK to call + // from two threads simultaneously + int hr = LocalDbCreateInstance(instanceInfo.version, instance, flags: 0); + SqlClientEventSource.Log.TryTraceEvent(" Starting creation of instance {0} version {1}", instance, instanceInfo.version); + + if (hr < 0) + { + throw CreateLocalDbException(errorMessage: StringsHelper.GetString("LocalDB_CreateFailed"), instance: instance, localDbError: hr); + } + + // Mark instance as created + SqlClientEventSource.Log.TryTraceEvent(" Finished creation of instance {0}", instance); + instanceInfo.created = true; + } + #endif + + #if NETFRAMEWORK + internal static void DemandLocalDbPermissions() + { + if (!s_partialTrustAllowed) + { + if (!s_partialTrustFlagChecked) + { + object partialTrustFlagValue = AppDomain.CurrentDomain.GetData(PartialTrustFlagKey); + if (partialTrustFlagValue is bool partialTrustFlagValueBool) + { + s_partialTrustAllowed = partialTrustFlagValueBool; + } + + s_partialTrustFlagChecked = true; + if (s_partialTrustAllowed) + { + return; + } + } + + s_fullTrust ??= new NamedPermissionSet("FullTrust"); + s_fullTrust.Demand(); + } + } + #endif + + // Check if name is in format (localdb)\ and return instance name if it is + // localDB can also have a format of np:\\.\pipe\LOCALDB#\tsql\query + internal static string GetLocalDbInstanceNameFromServerName(string serverName) + { + if (serverName is not null) + { + // it can start with spaces if specified in quotes + // Memory allocation is reduced by using ReadOnlySpan + ReadOnlySpan input = serverName.AsSpan().Trim(); + if (input.StartsWith(LocalDbPrefix.AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + input = input.Slice(LocalDbPrefix.Length); + if (!input.IsEmpty) + { + return input.ToString(); + } + } + else if (input.StartsWith(LocalDbPrefixNamedPipe.AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + return input.ToString(); + } + + } + return null; + } + + internal static string GetLocalDbMessage(int hrCode) + { + Debug.Assert(hrCode < 0, "HRCode does not indicate error"); + try + { + StringBuilder buffer = new StringBuilder(ErrorMessageBufferSize); + uint len = (uint)buffer.Capacity; + + // First try for current culture + int hResult = LocalDbFormatMessage( + hrLocalDb: hrCode, + dwFlags: LocalDbTruncateErrorMessage, + dwLanguageId: (uint)CultureInfo.CurrentCulture.LCID, + buffer: buffer, + bufferLength: ref len); + + if (hResult >= 0) + { + return buffer.ToString(); + } + + // Message is not available for current culture, try default + buffer = new StringBuilder(ErrorMessageBufferSize); + len = (uint)buffer.Capacity; + hResult = LocalDbFormatMessage( + hrLocalDb: hrCode, + dwFlags: LocalDbTruncateErrorMessage, + dwLanguageId: 0, // Thread locale with fallback to English + buffer: buffer, + bufferLength: ref len); + + if (hResult >= 0) + { + return buffer.ToString(); + } + + return string.Format(CultureInfo.CurrentCulture, "{0} (0x{1:X}).", Strings.LocalDB_UnobtainableMessage, hResult); + } + catch (SqlException exc) + { + return string.Format(CultureInfo.CurrentCulture, "{0} ({1}).", Strings.LocalDB_UnobtainableMessage, exc.Message); + } + } + + internal static void ReleaseDllHandles() + { + s_userInstanceDllHandle = IntPtr.Zero; + s_localDbFormatMessage = null; + + #if NETFRAMEWORK + s_localDbCreateInstance = null; + #endif + } + + private static SqlException CreateLocalDbException( + string errorMessage, + string instance = null, + int localDbError = 0, + uint sniError = 0) + { + Debug.Assert(localDbError == 0 || sniError == 0, "LocalDB error and SNI error cannot be specified simultaneously"); + Debug.Assert(!string.IsNullOrEmpty(errorMessage), "Error message should not be null or empty"); + + int errorCode = localDbError == 0 ? (int)sniError : localDbError; + + if (sniError != 0) + { + string sniErrorMessage = SQL.GetSNIErrorMessage(sniError); + errorMessage = $"{errorMessage} (error: {sniError} - {sniErrorMessage})"; + } + + SqlErrorCollection collection = new SqlErrorCollection() + { + new SqlError( + infoNumber: errorCode, + errorState: 0, + errorClass: TdsEnums.FATAL_ERROR_CLASS, + server: instance, + errorMessage: errorMessage, + procedure: null, + lineNumber: 0) + }; + + if (localDbError != 0) + { + collection.Add(new SqlError( + infoNumber: errorCode, + errorState: 0, + errorClass: TdsEnums.FATAL_ERROR_CLASS, + server: instance, + errorMessage: GetLocalDbMessage(localDbError), + procedure: null, + lineNumber: 0)); + } + + + SqlException exc = SqlException.CreateException(collection, null); + exc._doNotReconnect = true; + + return exc; + } + + private static IntPtr LoadProcAddress(string funcName) => + #if NETFRAMEWORK + Kernel32Safe.GetProcAddress(UserInstanceDllHandle, funcName); + #else + Kernel32.GetProcAddress(UserInstanceDllHandle, funcName); + #endif + + #if NETFRAMEWORK + private class InstanceInfo + { + internal InstanceInfo(string version) + { + this.version = version; + this.created = false; + } + + internal readonly string version; + internal bool created; + } + #endif + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbConfigurationSection.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbConfigurationSection.netfx.cs new file mode 100644 index 0000000000..4fdb66788b --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbConfigurationSection.netfx.cs @@ -0,0 +1,19 @@ +// 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. + +#if NETFRAMEWORK + +using System.Configuration; + +namespace Microsoft.Data.SqlClient.LocalDb +{ + internal sealed class LocalDbConfigurationSection : ConfigurationSection + { + [ConfigurationProperty("localdbinstances", IsRequired = true)] + public LocalDbInstancesCollection LocalDbInstances => + (LocalDbInstancesCollection)this["localdbinstances"] ?? new LocalDbInstancesCollection(); + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbInstanceElement.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbInstanceElement.netfx.cs new file mode 100644 index 0000000000..ef35b59116 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbInstanceElement.netfx.cs @@ -0,0 +1,21 @@ +// 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. + +#if NETFRAMEWORK + +using System.Configuration; + +namespace Microsoft.Data.SqlClient.LocalDb +{ + internal sealed class LocalDbInstanceElement : ConfigurationElement + { + [ConfigurationProperty("name", IsRequired = true)] + public string Name => this["name"] as string; + + [ConfigurationProperty("version", IsRequired = true)] + public string Version => this["version"] as string; + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbInstancesCollection.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbInstancesCollection.netfx.cs new file mode 100644 index 0000000000..64860a991a --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalDb/LocalDbInstancesCollection.netfx.cs @@ -0,0 +1,48 @@ +// 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. + +#if NETFRAMEWORK + +using System; +using System.Collections; +using System.Configuration; + +namespace Microsoft.Data.SqlClient.LocalDb +{ + internal sealed class LocalDbInstancesCollection : ConfigurationElementCollection + { + private static readonly TrimOrdinalIgnoreCaseStringComparer s_comparer = new TrimOrdinalIgnoreCaseStringComparer(); + + internal LocalDbInstancesCollection() + : base(s_comparer) + { + } + + protected override ConfigurationElement CreateNewElement() => + new LocalDbInstanceElement(); + + protected override object GetElementKey(ConfigurationElement element) => + ((LocalDbInstanceElement)element).Name; + + private class TrimOrdinalIgnoreCaseStringComparer : IComparer + { + public int Compare(object x, object y) + { + if (x is string xStr) + { + x = xStr.Trim(); + } + + if (y is string yStr) + { + y = yStr.Trim(); + } + + return StringComparer.OrdinalIgnoreCase.Compare(x, y); + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/ConcurrentQueueSemaphore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/ConcurrentQueueSemaphore.netcore.cs similarity index 97% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/ConcurrentQueueSemaphore.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/ConcurrentQueueSemaphore.netcore.cs index 46d3b70a25..fff46abea6 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/ConcurrentQueueSemaphore.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/ConcurrentQueueSemaphore.netcore.cs @@ -2,12 +2,13 @@ // 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; +#if NET + using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Data.SqlClient.SNI +namespace Microsoft.Data.SqlClient.ManagedSni { /// /// This class implements a FIFO Queue with SemaphoreSlim for ordered execution of parallel tasks. @@ -56,5 +57,6 @@ public void Release() _semaphore.Release(); } } - } + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/LocalDB.netcore.Unix.cs similarity index 89% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Unix.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/LocalDB.netcore.Unix.cs index a9b235d47c..8ab05865a2 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Unix.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/LocalDB.netcore.Unix.cs @@ -2,9 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; -namespace Microsoft.Data.SqlClient.SNI +namespace Microsoft.Data.SqlClient.ManagedSni { internal class LocalDB { @@ -14,3 +16,5 @@ internal static string GetLocalDBConnectionString(string localDbInstance) } } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/LocalDB.netcore.Windows.cs similarity index 91% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/LocalDB.netcore.Windows.cs index 6f8e855542..bcea4a8404 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/LocalDB.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/LocalDB.netcore.Windows.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; using System.Runtime.InteropServices; using System.Text; @@ -9,8 +11,7 @@ using Interop.Windows.Kernel32; using Microsoft.Win32; - -namespace Microsoft.Data.SqlClient.SNI +namespace Microsoft.Data.SqlClient.ManagedSni { internal sealed class LocalDB { @@ -55,7 +56,7 @@ private string GetConnectionString(string localDbInstance) int result = localDBStartInstanceFunc(localDbInstance, 0, localDBConnectionString, ref sizeOfbuffer); if (result != TdsEnums.SNI_SUCCESS) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBErrorCode, Strings.SNI_ERROR_50); + SniLoadHandle.SingletonInstance.LastError = new SniError(SniProviders.INVALID_PROV, 0, SniCommon.LocalDBErrorCode, Strings.SNI_ERROR_50); SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "Unsuccessful 'LocalDBStartInstance' method call with {0} result to start '{1}' localDb instance", args0: result, args1: localDbInstance); localDBConnectionString = null; } @@ -72,17 +73,17 @@ internal static uint MapLocalDBErrorStateToCode(LocalDBErrorState errorState) switch (errorState) { case LocalDBErrorState.NO_INSTALLATION: - return SNICommon.LocalDBNoInstallation; + return SniCommon.LocalDBNoInstallation; case LocalDBErrorState.INVALID_CONFIG: - return SNICommon.LocalDBInvalidConfig; + return SniCommon.LocalDBInvalidConfig; case LocalDBErrorState.NO_SQLUSERINSTANCEDLL_PATH: - return SNICommon.LocalDBNoSqlUserInstanceDllPath; + return SniCommon.LocalDBNoSqlUserInstanceDllPath; case LocalDBErrorState.INVALID_SQLUSERINSTANCEDLL_PATH: - return SNICommon.LocalDBInvalidSqlUserInstanceDllPath; + return SniCommon.LocalDBInvalidSqlUserInstanceDllPath; case LocalDBErrorState.NONE: return 0; default: - return SNICommon.LocalDBInvalidConfig; + return SniCommon.LocalDBInvalidConfig; } } @@ -133,7 +134,7 @@ private bool LoadUserInstanceDll() // If there was no DLL path found, then there is an error. if (dllPath == null) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, MapLocalDBErrorStateToCode(registryQueryErrorState), MapLocalDBErrorStateToErrorMessage(registryQueryErrorState)); + SniLoadHandle.SingletonInstance.LastError = new SniError(SniProviders.INVALID_PROV, 0, MapLocalDBErrorStateToCode(registryQueryErrorState), MapLocalDBErrorStateToErrorMessage(registryQueryErrorState)); SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "User instance DLL path is null."); return false; } @@ -141,7 +142,7 @@ private bool LoadUserInstanceDll() // In case the registry had an empty path for dll if (string.IsNullOrWhiteSpace(dllPath)) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBInvalidSqlUserInstanceDllPath, Strings.SNI_ERROR_55); + SniLoadHandle.SingletonInstance.LastError = new SniError(SniProviders.INVALID_PROV, 0, SniCommon.LocalDBInvalidSqlUserInstanceDllPath, Strings.SNI_ERROR_55); SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "User instance DLL path is invalid. DLL path = {0}", dllPath); return false; } @@ -151,7 +152,7 @@ private bool LoadUserInstanceDll() if (libraryHandle.IsInvalid) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBFailedToLoadDll, Strings.SNI_ERROR_56); + SniLoadHandle.SingletonInstance.LastError = new SniError(SniProviders.INVALID_PROV, 0, SniCommon.LocalDBFailedToLoadDll, Strings.SNI_ERROR_56); SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "Library Handle is invalid. Could not load the dll."); libraryHandle.Dispose(); return false; @@ -162,7 +163,7 @@ private bool LoadUserInstanceDll() if (_startInstanceHandle == IntPtr.Zero) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBBadRuntime, Strings.SNI_ERROR_57); + SniLoadHandle.SingletonInstance.LastError = new SniError(SniProviders.INVALID_PROV, 0, SniCommon.LocalDBBadRuntime, Strings.SNI_ERROR_57); SqlClientEventSource.Log.TrySNITraceEvent(nameof(LocalDB), EventType.ERR, "Was not able to load the PROC from DLL. Bad Runtime."); libraryHandle.Dispose(); return false; @@ -173,7 +174,7 @@ private bool LoadUserInstanceDll() if (localDBStartInstanceFunc == null) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBBadRuntime, Strings.SNI_ERROR_57); + SniLoadHandle.SingletonInstance.LastError = new SniError(SniProviders.INVALID_PROV, 0, SniCommon.LocalDBBadRuntime, Strings.SNI_ERROR_57); libraryHandle.Dispose(); _startInstanceHandle = IntPtr.Zero; return false; @@ -266,3 +267,5 @@ private string GetUserInstanceDllPath(out LocalDBErrorState errorState) } } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/ResolvedServerSpn.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/ResolvedServerSpn.cs new file mode 100644 index 0000000000..fe49c77f6b --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/ResolvedServerSpn.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +namespace Microsoft.Data.SqlClient.ManagedSni +{ + /// + /// This is used to hold the ServerSpn for a given connection. Most connection types have a single format, although TCP connections may allow + /// with and without a port. Depending on how the SPN is registered on the server, either one may be the correct name. + /// + /// + /// + /// + /// + /// SQL Server SPN format follows these patterns: + /// + /// + /// Default instance, no port (primary): + /// MSSQLSvc/fully-qualified-domain-name + /// + /// + /// Default instance, default port (secondary): + /// MSSQLSvc/fully-qualified-domain-name:1433 + /// + /// + /// Named instance or custom port: + /// MSSQLSvc/fully-qualified-domain-name:port_or_instance_name + /// + /// + /// For TCP connections to named instances, the port number is used in SPN. + /// For Named Pipe connections to named instances, the instance name is used in SPN. + /// When hostname resolution fails, the user-provided hostname is used instead of FQDN. + /// For default instances with TCP protocol, both forms (with and without port) may be returned. + /// + internal readonly struct ResolvedServerSpn(string primary, string? secondary = null) + { + public string Primary => primary; + + public string? Secondary => secondary; + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniAsyncCallback.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniAsyncCallback.netcore.cs new file mode 100644 index 0000000000..832d8bc1ce --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniAsyncCallback.netcore.cs @@ -0,0 +1,17 @@ +// 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. + +#if NET + +namespace Microsoft.Data.SqlClient.ManagedSni +{ + /// + /// SNI Asynchronous callback + /// + /// SNI packet + /// SNI error code + internal delegate void SniAsyncCallback(SniPacket packet, uint sniErrorCode); +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniCommon.netcore.cs similarity index 68% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniCommon.netcore.cs index f0eca54387..9653c94d34 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNICommon.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniCommon.netcore.cs @@ -2,115 +2,20 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; -using System.Buffers.Binary; using System.Diagnostics; using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Text; using Microsoft.Data.Common; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Data.ProviderBase; -namespace Microsoft.Data.SqlClient.SNI +namespace Microsoft.Data.SqlClient.ManagedSni { - /// - /// SNI Asynchronous callback - /// - /// SNI packet - /// SNI error code - internal delegate void SNIAsyncCallback(SNIPacket packet, uint sniErrorCode); - - /// - /// SNI provider identifiers - /// - internal enum SNIProviders - { - HTTP_PROV = 0, // HTTP Provider - NP_PROV = 1, // Named Pipes Provider - SESSION_PROV = 2, // Session Provider - SIGN_PROV = 3, // Sign Provider - SM_PROV = 4, // Shared Memory Provider - SMUX_PROV = 5, // SMUX Provider - SSL_PROV = 6, // SSL Provider - TCP_PROV = 7, // TCP Provider - VIA_PROV = 8, // Virtual Interface Architecture Provider - CTAIP_PROV = 9, - MAX_PROVS = 10, // Number of providers - INVALID_PROV = 11 // SQL Network Interfaces - } - - /// - /// SMUX packet header - /// - internal sealed class SNISMUXHeader - { - public const int HEADER_LENGTH = 16; - - public byte SMID; - public byte flags; - public ushort sessionId; - public uint length; - public uint sequenceNumber; - public uint highwater; - - public void Read(byte[] bytes) - { - SMID = bytes[0]; - flags = bytes[1]; - Span span = bytes.AsSpan(); - sessionId = BinaryPrimitives.ReadUInt16LittleEndian(span.Slice(2)); - length = BinaryPrimitives.ReadUInt32LittleEndian(span.Slice(4)) - SNISMUXHeader.HEADER_LENGTH; - sequenceNumber = BinaryPrimitives.ReadUInt32LittleEndian(span.Slice(8)); - highwater = BinaryPrimitives.ReadUInt32LittleEndian(span.Slice(12)); - } - - public void Write(Span bytes) - { - uint value = highwater; - // access the highest element first to cause the largest range check in the jit, then fill in the rest of the value and carry on as normal - bytes[15] = (byte)((value >> 24) & 0xff); - bytes[12] = (byte)(value & 0xff); // BitConverter.GetBytes(_currentHeader.highwater).CopyTo(headerBytes, 12); - bytes[13] = (byte)((value >> 8) & 0xff); - bytes[14] = (byte)((value >> 16) & 0xff); - - bytes[0] = SMID; // BitConverter.GetBytes(_currentHeader.SMID).CopyTo(headerBytes, 0); - bytes[1] = flags; // BitConverter.GetBytes(_currentHeader.flags).CopyTo(headerBytes, 1); - - value = sessionId; - bytes[2] = (byte)(value & 0xff); // BitConverter.GetBytes(_currentHeader.sessionId).CopyTo(headerBytes, 2); - bytes[3] = (byte)((value >> 8) & 0xff); - - value = length; - bytes[4] = (byte)(value & 0xff); // BitConverter.GetBytes(_currentHeader.length).CopyTo(headerBytes, 4); - bytes[5] = (byte)((value >> 8) & 0xff); - bytes[6] = (byte)((value >> 16) & 0xff); - bytes[7] = (byte)((value >> 24) & 0xff); - - value = sequenceNumber; - bytes[8] = (byte)(value & 0xff); // BitConverter.GetBytes(_currentHeader.sequenceNumber).CopyTo(headerBytes, 8); - bytes[9] = (byte)((value >> 8) & 0xff); - bytes[10] = (byte)((value >> 16) & 0xff); - bytes[11] = (byte)((value >> 24) & 0xff); - - } - } - - /// - /// SMUX packet flags - /// - [Flags] - internal enum SNISMUXFlags - { - SMUX_SYN = 1, // Begin SMUX connection - SMUX_ACK = 2, // Acknowledge SMUX packets - SMUX_FIN = 4, // End SMUX connection - SMUX_DATA = 8 // SMUX data packet - } - - internal class SNICommon + internal class SniCommon { // Each error number maps to SNI_ERROR_* in String.resx internal const int ConnTerminatedError = 2; @@ -151,11 +56,11 @@ internal class SNICommon /// True if certificate is valid internal static bool ValidateSslServerCertificate(Guid connectionId, string targetServerName, string hostNameInCertificate, X509Certificate serverCert, string validationCertFileName, SslPolicyErrors policyErrors) { - using (TrySNIEventScope.Create(nameof(SNICommon))) + using (TrySNIEventScope.Create(nameof(SniCommon))) { if (policyErrors == SslPolicyErrors.None) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.INFO, "Connection Id {0}, targetServerName {1}, SSL Server certificate not validated as PolicyErrors set to None.", args0: connectionId, args1: targetServerName); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.INFO, "Connection Id {0}, targetServerName {1}, SSL Server certificate not validated as PolicyErrors set to None.", args0: connectionId, args1: targetServerName); return true; } @@ -174,12 +79,16 @@ internal static bool ValidateSslServerCertificate(Guid connectionId, string targ { try { +#if NET9_0_OR_GREATER validationCertificate = X509CertificateLoader.LoadCertificateFromFile(validationCertFileName); +#else + validationCertificate = new X509Certificate(validationCertFileName); +#endif } catch (Exception e) { // if this fails, then fall back to the HostNameInCertificate or TargetServer validation. - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Exception occurred loading specified ServerCertificate: {1}, treating it as if ServerCertificate has not been specified.", args0: connectionId, args1: e.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Exception occurred loading specified ServerCertificate: {1}, treating it as if ServerCertificate has not been specified.", args0: connectionId, args1: e.Message); } } @@ -187,12 +96,12 @@ internal static bool ValidateSslServerCertificate(Guid connectionId, string targ { if (serverCert.GetRawCertData().AsSpan().SequenceEqual(validationCertificate.GetRawCertData().AsSpan())) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.INFO, "Connection Id {0}, ServerCertificate matches the certificate provided by the server. Certificate validation passed.", args0: connectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.INFO, "Connection Id {0}, ServerCertificate matches the certificate provided by the server. Certificate validation passed.", args0: connectionId); return true; } else { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.INFO, "Connection Id {0}, ServerCertificate doesn't match the certificate provided by the server. Certificate validation failed.", args0: connectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.INFO, "Connection Id {0}, ServerCertificate doesn't match the certificate provided by the server. Certificate validation failed.", args0: connectionId); throw ADP.SSLCertificateAuthenticationException(Strings.SQL_RemoteCertificateDoesNotMatchServerCertificate); } } @@ -202,13 +111,13 @@ internal static bool ValidateSslServerCertificate(Guid connectionId, string targ StringBuilder messageBuilder = new(); if (policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNotAvailable)) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "Connection Id {0}, targetServerName {1}, SSL Server certificate not validated as PolicyErrors set to RemoteCertificateNotAvailable.", args0: connectionId, args1: targetServerName); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.ERR, "Connection Id {0}, targetServerName {1}, SSL Server certificate not validated as PolicyErrors set to RemoteCertificateNotAvailable.", args0: connectionId, args1: targetServerName); messageBuilder.AppendLine(Strings.SQL_RemoteCertificateNotAvailable); } if (policyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors)) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "Connection Id {0}, targetServerName {0}, SslPolicyError {1}, SSL Policy certificate chain has errors.", args0: connectionId, args1: targetServerName, args2: policyErrors); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.ERR, "Connection Id {0}, targetServerName {0}, SslPolicyError {1}, SSL Policy certificate chain has errors.", args0: connectionId, args1: targetServerName, args2: policyErrors); // get the chain status from the certificate X509Certificate2 cert2 = serverCert as X509Certificate2; @@ -225,7 +134,7 @@ internal static bool ValidateSslServerCertificate(Guid connectionId, string targ chainStatusInformation.AppendLine(); } } - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "Connection Id {0}, targetServerName {1}, SslPolicyError {2}, SSL Policy certificate chain has errors. ChainStatus {3}", args0: connectionId, args1: targetServerName, args2: policyErrors, args3: chainStatusInformation); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.ERR, "Connection Id {0}, targetServerName {1}, SslPolicyError {2}, SSL Policy certificate chain has errors. ChainStatus {3}", args0: connectionId, args1: targetServerName, args2: policyErrors, args3: chainStatusInformation); messageBuilder.AppendFormat(Strings.SQL_RemoteCertificateChainErrors, chainStatusInformation); messageBuilder.AppendLine(); } @@ -235,7 +144,7 @@ internal static bool ValidateSslServerCertificate(Guid connectionId, string targ X509Certificate2 cert2 = serverCert as X509Certificate2; if (!cert2.MatchesHostname(serverNameToValidate)) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "Connection Id {0}, serverNameToValidate {1}, Target Server name or HNIC does not match the Subject/SAN in Certificate.", args0: connectionId, args1: serverNameToValidate); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.ERR, "Connection Id {0}, serverNameToValidate {1}, Target Server name or HNIC does not match the Subject/SAN in Certificate.", args0: connectionId, args1: serverNameToValidate); messageBuilder.AppendLine(Strings.SQL_RemoteCertificateNameMismatch); } } @@ -246,7 +155,7 @@ internal static bool ValidateSslServerCertificate(Guid connectionId, string targ } } - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.INFO, "Connection Id {0}, certificate with subject: {1}, validated successfully.", args0: connectionId, args1: serverCert.Subject); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.INFO, "Connection Id {0}, certificate with subject: {1}, validated successfully.", args0: connectionId, args1: serverCert.Subject); return true; } } @@ -265,9 +174,9 @@ internal static IPAddress[] GetDnsIpAddresses(string serverName, TimeoutTimer ti internal static IPAddress[] GetDnsIpAddresses(string serverName) { - using (TrySNIEventScope.Create(nameof(SNICommon))) + using (TrySNIEventScope.Create(nameof(SniCommon))) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.INFO, "Getting DNS host entries for serverName {0}.", args0: serverName); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.INFO, "Getting DNS host entries for serverName {0}.", args0: serverName); return Dns.GetHostAddresses(serverName); } } @@ -280,10 +189,10 @@ internal static IPAddress[] GetDnsIpAddresses(string serverName) /// SNI error code /// Error message /// - internal static uint ReportSNIError(SNIProviders provider, uint nativeError, uint sniError, string errorMessage) + internal static uint ReportSNIError(SniProviders provider, int nativeError, uint sniError, string errorMessage) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "Provider = {0}, native Error = {1}, SNI Error = {2}, Error Message = {3}", args0: provider, args1: nativeError, args2: sniError, args3: errorMessage); - return ReportSNIError(new SNIError(provider, nativeError, sniError, errorMessage)); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.ERR, "Provider = {0}, native Error = {1}, SNI Error = {2}, Error Message = {3}", args0: provider, args1: nativeError, args2: sniError, args3: errorMessage); + return ReportSNIError(new SniError(provider, nativeError, sniError, errorMessage)); } /// @@ -294,10 +203,10 @@ internal static uint ReportSNIError(SNIProviders provider, uint nativeError, uin /// SNI Exception /// Native SNI error code /// - internal static uint ReportSNIError(SNIProviders provider, uint sniError, Exception sniException, uint nativeErrorCode = 0) + internal static uint ReportSNIError(SniProviders provider, uint sniError, Exception sniException, int nativeErrorCode = 0) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNICommon), EventType.ERR, "Provider = {0}, SNI Error = {1}, Exception = {2}", args0: provider, args1: sniError, args2: sniException?.Message); - return ReportSNIError(new SNIError(provider, sniError, sniException, nativeErrorCode)); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniCommon), EventType.ERR, "Provider = {0}, SNI Error = {1}, Exception = {2}", args0: provider, args1: sniError, args2: sniException?.Message); + return ReportSNIError(new SniError(provider, sniError, sniException, nativeErrorCode)); } /// @@ -305,10 +214,12 @@ internal static uint ReportSNIError(SNIProviders provider, uint sniError, Except /// /// SNI error /// - internal static uint ReportSNIError(SNIError error) + internal static uint ReportSNIError(SniError error) { - SNILoadHandle.SingletonInstance.LastError = error; + SniLoadHandle.SingletonInstance.LastError = error; return TdsEnums.SNI_ERROR; } } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniError.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniError.netcore.cs new file mode 100644 index 0000000000..9cecb1e70f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniError.netcore.cs @@ -0,0 +1,67 @@ +// 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. + +#if NET + +using System; +using System.ComponentModel; +using System.Net.Sockets; + +namespace Microsoft.Data.SqlClient.ManagedSni +{ + /// + /// SNI error + /// + internal class SniError + { + // Error numbers from native SNI implementation + // This is signed int representation of the error code 0x80090325 + internal const int CertificateValidationErrorCode = -2146893019; + + public readonly SniProviders provider; + public readonly string errorMessage; + public readonly int nativeError; + public readonly uint sniError; + public readonly string function; + public readonly uint lineNumber; + public readonly Exception exception; + + public SniError(SniProviders provider, int nativeError, uint sniErrorCode, string errorMessage) + { + lineNumber = 0; + function = string.Empty; + this.provider = provider; + this.nativeError = nativeError; + sniError = sniErrorCode; + this.errorMessage = errorMessage; + exception = null; + } + + public SniError(SniProviders provider, uint sniErrorCode, Exception sniException, int nativeErrorCode = 0) + { + lineNumber = 0; + function = string.Empty; + this.provider = provider; + nativeError = nativeErrorCode; + if (nativeErrorCode == 0) + { + if (sniException is SocketException socketException) + { + // SocketErrorCode values are cross-plat consistent in .NET (matching native Windows error codes) + // underlying type of SocketErrorCode is int + nativeError = (int)socketException.SocketErrorCode; + } + else if (sniException is Win32Exception win32Exception) + { + nativeError = win32Exception.NativeErrorCode; // Replicates native SNI behavior + } + } + sniError = sniErrorCode; + errorMessage = string.Empty; + exception = sniException; + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniHandle.netcore.cs similarity index 86% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniHandle.netcore.cs index 515686f6a3..1f3ee01ab8 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIHandle.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniHandle.netcore.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; using System.Collections.Generic; using System.Net.Security; @@ -10,12 +12,12 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Data.SqlClient.SNI +namespace Microsoft.Data.SqlClient.ManagedSni { /// /// SNI connection handle /// - internal abstract class SNIHandle + internal abstract class SniHandle { protected static readonly SslProtocols s_supportedProtocols = SslProtocols.None; @@ -47,7 +49,7 @@ protected static void AuthenticateAsClient(SslStream sslStream, string serverNam /// /// Receive callback /// Send callback - public abstract void SetAsyncCallbacks(SNIAsyncCallback receiveCallback, SNIAsyncCallback sendCallback); + public abstract void SetAsyncCallbacks(SniAsyncCallback receiveCallback, SniAsyncCallback sendCallback); /// /// Set buffer size @@ -60,14 +62,14 @@ protected static void AuthenticateAsClient(SslStream sslStream, string serverNam /// /// SNI packet /// SNI error code - public abstract uint Send(SNIPacket packet); + public abstract uint Send(SniPacket packet); /// /// Send a packet asynchronously /// /// SNI packet /// SNI error code - public abstract uint SendAsync(SNIPacket packet); + public abstract uint SendAsync(SniPacket packet); /// /// Receive a packet synchronously @@ -75,14 +77,14 @@ protected static void AuthenticateAsClient(SslStream sslStream, string serverNam /// SNI packet /// Timeout in Milliseconds /// SNI error code - public abstract uint Receive(out SNIPacket packet, int timeoutInMilliseconds); + public abstract uint Receive(out SniPacket packet, int timeoutInMilliseconds); /// /// Receive a packet asynchronously /// /// SNI packet /// SNI error code - public abstract uint ReceiveAsync(ref SNIPacket packet); + public abstract uint ReceiveAsync(ref SniPacket packet); /// /// Enable SSL @@ -112,19 +114,22 @@ protected static void AuthenticateAsClient(SslStream sslStream, string serverNam public virtual int ReserveHeaderSize => 0; - public abstract SNIPacket RentPacket(int headerSize, int dataSize); + public abstract SniPacket RentPacket(int headerSize, int dataSize); - public abstract void ReturnPacket(SNIPacket packet); + public abstract void ReturnPacket(SniPacket packet); /// /// Gets a value that indicates the security protocol used to authenticate this connection. /// public virtual int ProtocolVersion { get; } = 0; -#if DEBUG + + #if DEBUG /// /// Test handle for killing underlying connection /// public abstract void KillConnection(); -#endif + #endif } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNILoadHandle.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniLoadHandle.netcore.cs similarity index 80% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNILoadHandle.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniLoadHandle.netcore.cs index c1a5e1a573..5c0d85ba67 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNILoadHandle.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniLoadHandle.netcore.cs @@ -2,26 +2,28 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System.Threading; -namespace Microsoft.Data.SqlClient.SNI +namespace Microsoft.Data.SqlClient.ManagedSni { /// /// Global SNI settings and status /// - internal class SNILoadHandle + internal class SniLoadHandle { - public static readonly SNILoadHandle SingletonInstance = new SNILoadHandle(); + public static readonly SniLoadHandle SingletonInstance = new SniLoadHandle(); public readonly EncryptionOptions _encryptionOption = EncryptionOptions.OFF; - public ThreadLocal _lastError = new ThreadLocal(static () => new SNIError(SNIProviders.INVALID_PROV, 0, TdsEnums.SNI_SUCCESS, string.Empty)); + public ThreadLocal _lastError = new ThreadLocal(static () => new SniError(SniProviders.INVALID_PROV, 0, TdsEnums.SNI_SUCCESS, string.Empty)); private readonly uint _status = TdsEnums.SNI_SUCCESS; /// /// Last SNI error /// - public SNIError LastError + public SniError LastError { get { @@ -63,3 +65,5 @@ public EncryptionOptions Options public bool ClientOSEncryptionSupport => true; } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniMarsConnection.netcore.cs similarity index 76% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsConnection.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniMarsConnection.netcore.cs index f929a1ba32..030dfb7452 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniMarsConnection.netcore.cs @@ -2,28 +2,30 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; -namespace Microsoft.Data.SqlClient.SNI +namespace Microsoft.Data.SqlClient.ManagedSni { /// /// SNI MARS connection. Multiple MARS streams will be overlaid on this connection. /// - internal class SNIMarsConnection + internal class SniMarsConnection { private readonly Guid _connectionId; - private readonly Dictionary _sessions; + private readonly Dictionary _sessions; private readonly byte[] _headerBytes; - private readonly SNISMUXHeader _currentHeader; + private readonly SniSmuxHeader _currentHeader; private readonly object _sync; - private SNIHandle _lowerHandle; + private SniHandle _lowerHandle; private ushort _nextSessionId; private int _currentHeaderByteCount; private int _dataBytesLeft; - private SNIPacket _currentPacket; + private SniPacket _currentPacket; /// /// Connection ID @@ -38,29 +40,29 @@ internal class SNIMarsConnection /// Constructor /// /// Lower handle - public SNIMarsConnection(SNIHandle lowerHandle) + public SniMarsConnection(SniHandle lowerHandle) { _sync = new object(); _connectionId = Guid.NewGuid(); - _sessions = new Dictionary(); - _headerBytes = new byte[SNISMUXHeader.HEADER_LENGTH]; - _currentHeader = new SNISMUXHeader(); + _sessions = new Dictionary(); + _headerBytes = new byte[SniSmuxHeader.HEADER_LENGTH]; + _currentHeader = new SniSmuxHeader(); _nextSessionId = 0; _currentHeaderByteCount = 0; _dataBytesLeft = 0; _lowerHandle = lowerHandle; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.INFO, "Created MARS Session Id {0}", args0: ConnectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.INFO, "Created MARS Session Id {0}", args0: ConnectionId); _lowerHandle.SetAsyncCallbacks(HandleReceiveComplete, HandleSendComplete); } - public SNIMarsHandle CreateMarsSession(object callbackObject, bool async) + public SniMarsHandle CreateMarsSession(object callbackObject, bool async) { lock (DemuxerSync) { ushort sessionId = _nextSessionId++; - SNIMarsHandle handle = new SNIMarsHandle(this, sessionId, callbackObject, async); + SniMarsHandle handle = new SniMarsHandle(this, sessionId, callbackObject, async); _sessions.Add(sessionId, handle); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.INFO, "MARS Session Id {0}, SNI MARS Handle Id {1}, created new MARS Session {2}", args0: ConnectionId, args1: handle?.ConnectionId, args2: sessionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.INFO, "MARS Session Id {0}, SNI MARS Handle Id {1}, created new MARS Session {2}", args0: ConnectionId, args1: handle?.ConnectionId, args2: sessionId); return handle; } } @@ -71,17 +73,17 @@ public SNIMarsHandle CreateMarsSession(object callbackObject, bool async) /// public uint StartReceive() { - using (TrySNIEventScope.Create(nameof(SNIMarsConnection))) + using (TrySNIEventScope.Create(nameof(SniMarsConnection))) { - SNIPacket packet = null; + SniPacket packet = null; if (ReceiveAsync(ref packet) == TdsEnums.SNI_SUCCESS_IO_PENDING) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.INFO, "MARS Session Id {0}, Success IO pending.", args0: ConnectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.INFO, "MARS Session Id {0}, Success IO pending.", args0: ConnectionId); return TdsEnums.SNI_SUCCESS_IO_PENDING; } - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.ERR, "MARS Session Id {0}, Connection not usable.", args0: ConnectionId); - return SNICommon.ReportSNIError(SNIProviders.SMUX_PROV, 0, SNICommon.ConnNotUsableError, Strings.SNI_ERROR_19); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.ERR, "MARS Session Id {0}, Connection not usable.", args0: ConnectionId); + return SniCommon.ReportSNIError(SniProviders.SMUX_PROV, 0, SniCommon.ConnNotUsableError, Strings.SNI_ERROR_19); } } @@ -90,9 +92,9 @@ public uint StartReceive() /// /// SNI packet /// SNI error code - public uint Send(SNIPacket packet) + public uint Send(SniPacket packet) { - using (TrySNIEventScope.Create(nameof(SNIMarsConnection))) + using (TrySNIEventScope.Create(nameof(SniMarsConnection))) { lock (DemuxerSync) { @@ -106,9 +108,9 @@ public uint Send(SNIPacket packet) /// /// SNI packet /// SNI error code - public uint SendAsync(SNIPacket packet) + public uint SendAsync(SniPacket packet) { - using (TrySNIEventScope.Create(nameof(SNIMarsConnection))) + using (TrySNIEventScope.Create(nameof(SniMarsConnection))) { lock (DemuxerSync) { @@ -122,15 +124,15 @@ public uint SendAsync(SNIPacket packet) /// /// SNI packet /// SNI error code - public uint ReceiveAsync(ref SNIPacket packet) + public uint ReceiveAsync(ref SniPacket packet) { - using (TrySNIEventScope.Create(nameof(SNIMarsConnection))) + using (TrySNIEventScope.Create(nameof(SniMarsConnection))) { if (packet != null) { ReturnPacket(packet); #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.INFO, "MARS Session Id {0}, Packet {1} returned", args0: ConnectionId, args1: packet?._id); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.INFO, "MARS Session Id {0}, Packet {1} returned", args0: ConnectionId, args1: packet?._id); #endif packet = null; } @@ -139,7 +141,7 @@ public uint ReceiveAsync(ref SNIPacket packet) { var response = _lowerHandle.ReceiveAsync(ref packet); #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.INFO, "MARS Session Id {0}, Received new packet {1}", args0: ConnectionId, args1: packet?._id); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.INFO, "MARS Session Id {0}, Received new packet {1}", args0: ConnectionId, args1: packet?._id); #endif return response; } @@ -152,7 +154,7 @@ public uint ReceiveAsync(ref SNIPacket packet) /// SNI error status public uint CheckConnection() { - using (TrySNIEventScope.Create(nameof(SNIMarsConnection))) + using (TrySNIEventScope.Create(nameof(SniMarsConnection))) { lock (DemuxerSync) { @@ -164,20 +166,20 @@ public uint CheckConnection() /// /// Process a receive error /// - public void HandleReceiveError(SNIPacket packet) + public void HandleReceiveError(SniPacket packet) { Debug.Assert(Monitor.IsEntered(this), "HandleReceiveError was called without being locked."); - foreach (SNIMarsHandle handle in _sessions.Values) + foreach (SniMarsHandle handle in _sessions.Values) { if (packet.HasAsyncIOCompletionCallback) { handle.HandleReceiveError(packet); #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.ERR, "MARS Session Id {0}, Packet {1} has Completion Callback", args0: ConnectionId, args1: packet?._id); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.ERR, "MARS Session Id {0}, Packet {1} has Completion Callback", args0: ConnectionId, args1: packet?._id); } else { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.ERR, "MARS Session Id {0}, Packet {1} does not have Completion Callback, error not handled.", args0: ConnectionId, args1: packet?._id); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.ERR, "MARS Session Id {0}, Packet {1} does not have Completion Callback, error not handled.", args0: ConnectionId, args1: packet?._id); #endif } } @@ -190,7 +192,7 @@ public void HandleReceiveError(SNIPacket packet) /// /// SNI packet /// SNI error code - public void HandleSendComplete(SNIPacket packet, uint sniErrorCode) + public void HandleSendComplete(SniPacket packet, uint sniErrorCode) { packet.InvokeAsyncIOCompletionCallback(sniErrorCode); } @@ -200,20 +202,20 @@ public void HandleSendComplete(SNIPacket packet, uint sniErrorCode) /// /// SNI packet /// SNI error code - public void HandleReceiveComplete(SNIPacket packet, uint sniErrorCode) + public void HandleReceiveComplete(SniPacket packet, uint sniErrorCode) { - using (TrySNIEventScope.Create(nameof(SNIMarsConnection))) + using (TrySNIEventScope.Create(nameof(SniMarsConnection))) { - SNISMUXHeader currentHeader = null; - SNIPacket currentPacket = null; - SNIMarsHandle currentSession = null; + SniSmuxHeader currentHeader = null; + SniPacket currentPacket = null; + SniMarsHandle currentSession = null; if (sniErrorCode != TdsEnums.SNI_SUCCESS) { lock (DemuxerSync) { HandleReceiveError(packet); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.ERR, "MARS Session Id {0}, Handled receive error code: {1}", args0: _lowerHandle?.ConnectionId, args1: sniErrorCode); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.ERR, "MARS Session Id {0}, Handled receive error code: {1}", args0: _lowerHandle?.ConnectionId, args1: sniErrorCode); return; } } @@ -222,21 +224,21 @@ public void HandleReceiveComplete(SNIPacket packet, uint sniErrorCode) { lock (DemuxerSync) { - if (_currentHeaderByteCount != SNISMUXHeader.HEADER_LENGTH) + if (_currentHeaderByteCount != SniSmuxHeader.HEADER_LENGTH) { currentHeader = null; currentPacket = null; currentSession = null; - while (_currentHeaderByteCount != SNISMUXHeader.HEADER_LENGTH) + while (_currentHeaderByteCount != SniSmuxHeader.HEADER_LENGTH) { - int bytesTaken = packet.TakeData(_headerBytes, _currentHeaderByteCount, SNISMUXHeader.HEADER_LENGTH - _currentHeaderByteCount); + int bytesTaken = packet.TakeData(_headerBytes, _currentHeaderByteCount, SniSmuxHeader.HEADER_LENGTH - _currentHeaderByteCount); _currentHeaderByteCount += bytesTaken; if (bytesTaken == 0) { sniErrorCode = ReceiveAsync(ref packet); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.INFO, "MARS Session Id {0}, Non-SMUX Header SNI Packet received with code {1}", args0: ConnectionId, args1: sniErrorCode); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.INFO, "MARS Session Id {0}, Non-SMUX Header SNI Packet received with code {1}", args0: ConnectionId, args1: sniErrorCode); if (sniErrorCode == TdsEnums.SNI_SUCCESS_IO_PENDING) { @@ -244,7 +246,7 @@ public void HandleReceiveComplete(SNIPacket packet, uint sniErrorCode) } HandleReceiveError(packet); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.ERR, "MARS Session Id {0}, Handled receive error code: {1}", args0: _lowerHandle?.ConnectionId, args1: sniErrorCode); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.ERR, "MARS Session Id {0}, Handled receive error code: {1}", args0: _lowerHandle?.ConnectionId, args1: sniErrorCode); return; } } @@ -253,14 +255,14 @@ public void HandleReceiveComplete(SNIPacket packet, uint sniErrorCode) _dataBytesLeft = (int)_currentHeader.length; _currentPacket = _lowerHandle.RentPacket(headerSize: 0, dataSize: (int)_currentHeader.length); #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.INFO, "MARS Session Id {0}, _dataBytesLeft {1}, _currentPacket {2}, Reading data of length: _currentHeader.length {3}", args0: _lowerHandle?.ConnectionId, args1: _dataBytesLeft, args2: currentPacket?._id, args3: _currentHeader?.length); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.INFO, "MARS Session Id {0}, _dataBytesLeft {1}, _currentPacket {2}, Reading data of length: _currentHeader.length {3}", args0: _lowerHandle?.ConnectionId, args1: _dataBytesLeft, args2: currentPacket?._id, args3: _currentHeader?.length); #endif } currentHeader = _currentHeader; currentPacket = _currentPacket; - if (_currentHeader.flags == (byte)SNISMUXFlags.SMUX_DATA) + if (_currentHeader.flags == (byte)SniSmuxFlags.SMUX_DATA) { if (_dataBytesLeft > 0) { @@ -270,7 +272,7 @@ public void HandleReceiveComplete(SNIPacket packet, uint sniErrorCode) if (_dataBytesLeft > 0) { sniErrorCode = ReceiveAsync(ref packet); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.INFO, "MARS Session Id {0}, SMUX DATA Header SNI Packet received with code {1}, _dataBytesLeft {2}", args0: ConnectionId, args1: sniErrorCode, args2: _dataBytesLeft); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.INFO, "MARS Session Id {0}, SMUX DATA Header SNI Packet received with code {1}, _dataBytesLeft {2}", args0: ConnectionId, args1: sniErrorCode, args2: _dataBytesLeft); if (sniErrorCode == TdsEnums.SNI_SUCCESS_IO_PENDING) { @@ -278,7 +280,7 @@ public void HandleReceiveComplete(SNIPacket packet, uint sniErrorCode) } HandleReceiveError(packet); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.ERR, "MARS Session Id {0}, Handled receive error code: {1}", args0: _lowerHandle?.ConnectionId, args1: sniErrorCode); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.ERR, "MARS Session Id {0}, Handled receive error code: {1}", args0: _lowerHandle?.ConnectionId, args1: sniErrorCode); return; } } @@ -288,47 +290,47 @@ public void HandleReceiveComplete(SNIPacket packet, uint sniErrorCode) if (!_sessions.ContainsKey(_currentHeader.sessionId)) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.SMUX_PROV, 0, SNICommon.InvalidParameterError, Strings.SNI_ERROR_5); + SniLoadHandle.SingletonInstance.LastError = new SniError(SniProviders.SMUX_PROV, 0, SniCommon.InvalidParameterError, Strings.SNI_ERROR_5); HandleReceiveError(packet); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.ERR, "Current Header Session Id {0} not found, MARS Session Id {1} will be destroyed, New SNI error created: {2}", args0: _currentHeader?.sessionId, args1: _lowerHandle?.ConnectionId, args2: sniErrorCode); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.ERR, "Current Header Session Id {0} not found, MARS Session Id {1} will be destroyed, New SNI error created: {2}", args0: _currentHeader?.sessionId, args1: _lowerHandle?.ConnectionId, args2: sniErrorCode); _lowerHandle.Dispose(); _lowerHandle = null; return; } - if (_currentHeader.flags == (byte)SNISMUXFlags.SMUX_FIN) + if (_currentHeader.flags == (byte)SniSmuxFlags.SMUX_FIN) { _sessions.Remove(_currentHeader.sessionId); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.INFO, "SMUX_FIN | MARS Session Id {0}, SMUX_FIN flag received, Current Header Session Id {1} removed", args0: _lowerHandle?.ConnectionId, args1: _currentHeader?.sessionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.INFO, "SMUX_FIN | MARS Session Id {0}, SMUX_FIN flag received, Current Header Session Id {1} removed", args0: _lowerHandle?.ConnectionId, args1: _currentHeader?.sessionId); } else { currentSession = _sessions[_currentHeader.sessionId]; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.INFO, "MARS Session Id {0}, Current Session assigned to Session Id {1}", args0: _lowerHandle?.ConnectionId, args1: _currentHeader?.sessionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.INFO, "MARS Session Id {0}, Current Session assigned to Session Id {1}", args0: _lowerHandle?.ConnectionId, args1: _currentHeader?.sessionId); } } - if (currentHeader.flags == (byte)SNISMUXFlags.SMUX_DATA) + if (currentHeader.flags == (byte)SniSmuxFlags.SMUX_DATA) { currentSession.HandleReceiveComplete(currentPacket, currentHeader); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.INFO, "SMUX_DATA | MARS Session Id {0}, Current Session {1} completed receiving Data", args0: _lowerHandle?.ConnectionId, args1: _currentHeader?.sessionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.INFO, "SMUX_DATA | MARS Session Id {0}, Current Session {1} completed receiving Data", args0: _lowerHandle?.ConnectionId, args1: _currentHeader?.sessionId); } - if (_currentHeader.flags == (byte)SNISMUXFlags.SMUX_ACK) + if (_currentHeader.flags == (byte)SniSmuxFlags.SMUX_ACK) { try { currentSession.HandleAck(currentHeader.highwater); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.INFO, "SMUX_ACK | MARS Session Id {0}, Current Session {1} handled ack", args0: _lowerHandle?.ConnectionId, args1: _currentHeader?.sessionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.INFO, "SMUX_ACK | MARS Session Id {0}, Current Session {1} handled ack", args0: _lowerHandle?.ConnectionId, args1: _currentHeader?.sessionId); } catch (Exception e) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.ERR, "SMUX_ACK | MARS Session Id {0}, Exception occurred: {2}", args0: _currentHeader?.sessionId, args1: e?.Message); - SNICommon.ReportSNIError(SNIProviders.SMUX_PROV, SNICommon.InternalExceptionError, e); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.ERR, "SMUX_ACK | MARS Session Id {0}, Exception occurred: {2}", args0: _currentHeader?.sessionId, args1: e?.Message); + SniCommon.ReportSNIError(SniProviders.SMUX_PROV, SniCommon.InternalExceptionError, e); } #if DEBUG Debug.Assert(_currentPacket == currentPacket, "current and _current are not the same"); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.INFO, "SMUX_ACK | MARS Session Id {0}, Current Packet {1} returned", args0: _lowerHandle?.ConnectionId, args1: currentPacket?._id); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.INFO, "SMUX_ACK | MARS Session Id {0}, Current Packet {1} returned", args0: _lowerHandle?.ConnectionId, args1: currentPacket?._id); #endif ReturnPacket(currentPacket); currentPacket = null; @@ -347,7 +349,7 @@ public void HandleReceiveComplete(SNIPacket packet, uint sniErrorCode) } HandleReceiveError(packet); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsConnection), EventType.ERR, "MARS Session Id {0}, packet.DataLeft 0, SNI error {2}", args0: _lowerHandle?.ConnectionId, args1: sniErrorCode); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsConnection), EventType.ERR, "MARS Session Id {0}, packet.DataLeft 0, SNI error {2}", args0: _lowerHandle?.ConnectionId, args1: sniErrorCode); return; } } @@ -360,7 +362,7 @@ public void HandleReceiveComplete(SNIPacket packet, uint sniErrorCode) /// public uint EnableSsl(uint options) { - using (TrySNIEventScope.Create(nameof(SNIMarsConnection))) + using (TrySNIEventScope.Create(nameof(SniMarsConnection))) { return _lowerHandle.EnableSsl(options); } @@ -371,33 +373,35 @@ public uint EnableSsl(uint options) /// public void DisableSsl() { - using (TrySNIEventScope.Create(nameof(SNIMarsConnection))) + using (TrySNIEventScope.Create(nameof(SniMarsConnection))) { _lowerHandle.DisableSsl(); } } - public SNIPacket RentPacket(int headerSize, int dataSize) + public SniPacket RentPacket(int headerSize, int dataSize) { return _lowerHandle.RentPacket(headerSize, dataSize); } - public void ReturnPacket(SNIPacket packet) + public void ReturnPacket(SniPacket packet) { _lowerHandle.ReturnPacket(packet); } -#if DEBUG + #if DEBUG /// /// Test handle for killing underlying connection /// public void KillConnection() { - using (TrySNIEventScope.Create(nameof(SNIMarsConnection))) + using (TrySNIEventScope.Create(nameof(SniMarsConnection))) { _lowerHandle.KillConnection(); } } -#endif + #endif } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsHandle.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniMarsHandle.netcore.cs similarity index 78% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsHandle.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniMarsHandle.netcore.cs index b7f682f6d5..ed69c242bf 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIMarsHandle.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniMarsHandle.netcore.cs @@ -2,38 +2,40 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; -namespace Microsoft.Data.SqlClient.SNI +namespace Microsoft.Data.SqlClient.ManagedSni { /// /// MARS handle /// - internal sealed class SNIMarsHandle : SNIHandle + internal sealed class SniMarsHandle : SniHandle { private const uint ACK_THRESHOLD = 2; - private readonly SNIMarsConnection _connection; + private readonly SniMarsConnection _connection; private readonly uint _status = TdsEnums.SNI_UNINITIALIZED; - private readonly Queue _receivedPacketQueue = new Queue(); - private readonly Queue _sendPacketQueue = new Queue(); + private readonly Queue _receivedPacketQueue = new Queue(); + private readonly Queue _sendPacketQueue = new Queue(); private readonly object _callbackObject; private readonly Guid _connectionId; private readonly ushort _sessionId; private readonly ManualResetEventSlim _packetEvent = new ManualResetEventSlim(false); private readonly ManualResetEventSlim _ackEvent = new ManualResetEventSlim(false); - private readonly SNISMUXHeader _currentHeader = new SNISMUXHeader(); - private readonly SNIAsyncCallback _handleSendCompleteCallback; + private readonly SniSmuxHeader _currentHeader = new SniSmuxHeader(); + private readonly SniAsyncCallback _handleSendCompleteCallback; private uint _sendHighwater = 4; private int _asyncReceives = 0; private uint _receiveHighwater = 4; private uint _receiveHighwaterLastAck = 4; private uint _sequenceNumber; - private SNIError _connectionError; + private SniError _connectionError; /// /// Connection ID @@ -45,7 +47,7 @@ internal sealed class SNIMarsHandle : SNIHandle /// public override uint Status => _status; - public override int ReserveHeaderSize => SNISMUXHeader.HEADER_LENGTH; + public override int ReserveHeaderSize => SniSmuxHeader.HEADER_LENGTH; public override int ProtocolVersion => _connection.ProtocolVersion; @@ -54,17 +56,17 @@ internal sealed class SNIMarsHandle : SNIHandle /// public override void Dispose() { - using (TrySNIEventScope.Create(nameof(SNIMarsHandle))) + using (TrySNIEventScope.Create(nameof(SniMarsHandle))) { try { - SendControlPacket(SNISMUXFlags.SMUX_FIN); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, Sent SMUX_FIN packet to terminate session.", args0: ConnectionId); + SendControlPacket(SniSmuxFlags.SMUX_FIN); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, Sent SMUX_FIN packet to terminate session.", args0: ConnectionId); } catch (Exception e) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.ERR, "MARS Session Id {0}, Internal exception error = {1}, Member Name={2}", args0: ConnectionId, args1: e?.Message, args2: e?.GetType()?.Name); - SNICommon.ReportSNIError(SNIProviders.SMUX_PROV, SNICommon.InternalExceptionError, e); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.ERR, "MARS Session Id {0}, Internal exception error = {1}, Member Name={2}", args0: ConnectionId, args1: e?.Message, args2: e?.GetType()?.Name); + SniCommon.ReportSNIError(SniProviders.SMUX_PROV, SniCommon.InternalExceptionError, e); } } } @@ -76,15 +78,15 @@ public override void Dispose() /// MARS session ID /// Callback object /// true if connection is asynchronous - public SNIMarsHandle(SNIMarsConnection connection, ushort sessionId, object callbackObject, bool async) + public SniMarsHandle(SniMarsConnection connection, ushort sessionId, object callbackObject, bool async) { _sessionId = sessionId; _connection = connection; _connectionId = connection.ConnectionId; _callbackObject = callbackObject; _handleSendCompleteCallback = HandleSendComplete; - SendControlPacket(SNISMUXFlags.SMUX_SYN); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, Sent SMUX_SYN packet to start a new session, session Id {1}", args0: ConnectionId, args1: _sessionId); + SendControlPacket(SniSmuxFlags.SMUX_SYN); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, Sent SMUX_SYN packet to start a new session, session Id {1}", args0: ConnectionId, args1: _sessionId); _status = TdsEnums.SNI_SUCCESS; } @@ -92,39 +94,39 @@ public SNIMarsHandle(SNIMarsConnection connection, ushort sessionId, object call /// Send control packet /// /// SMUX header flags - private void SendControlPacket(SNISMUXFlags flags) + private void SendControlPacket(SniSmuxFlags flags) { - using (TrySNIEventScope.Create(nameof(SNIMarsHandle))) + using (TrySNIEventScope.Create(nameof(SniMarsHandle))) { - SNIPacket packet = RentPacket(headerSize: SNISMUXHeader.HEADER_LENGTH, dataSize: 0); + SniPacket packet = RentPacket(headerSize: SniSmuxHeader.HEADER_LENGTH, dataSize: 0); #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, Packet rented {1}, packet dataLeft {2}", args0: ConnectionId, args1: packet?._id, args2: packet?.DataLeft); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, Packet rented {1}, packet dataLeft {2}", args0: ConnectionId, args1: packet?._id, args2: packet?.DataLeft); #endif lock (this) { SetupSMUXHeader(0, flags); - _currentHeader.Write(packet.GetHeaderBuffer(SNISMUXHeader.HEADER_LENGTH)); + _currentHeader.Write(packet.GetHeaderBuffer(SniSmuxHeader.HEADER_LENGTH)); packet.SetHeaderActive(); } _connection.Send(packet); ReturnPacket(packet); #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, Packet returned {1}, packet dataLeft {2}", args0: ConnectionId, args1: packet?._id, args2: packet?.DataLeft); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, Packet returned {1}, packet dataLeft {2}", args0: ConnectionId, args1: packet?._id, args2: packet?.DataLeft); ; #endif } } - private void SetupSMUXHeader(int length, SNISMUXFlags flags) + private void SetupSMUXHeader(int length, SniSmuxFlags flags) { Debug.Assert(Monitor.IsEntered(this), "must take lock on self before updating smux header"); _currentHeader.SMID = 83; _currentHeader.flags = (byte)flags; _currentHeader.sessionId = _sessionId; - _currentHeader.length = (uint)SNISMUXHeader.HEADER_LENGTH + (uint)length; - _currentHeader.sequenceNumber = ((flags == SNISMUXFlags.SMUX_FIN) || (flags == SNISMUXFlags.SMUX_ACK)) ? _sequenceNumber - 1 : _sequenceNumber++; + _currentHeader.length = (uint)SniSmuxHeader.HEADER_LENGTH + (uint)length; + _currentHeader.sequenceNumber = ((flags == SniSmuxFlags.SMUX_FIN) || (flags == SniSmuxFlags.SMUX_ACK)) ? _sequenceNumber - 1 : _sequenceNumber++; _currentHeader.highwater = _receiveHighwater; _receiveHighwaterLastAck = _currentHeader.highwater; } @@ -134,15 +136,15 @@ private void SetupSMUXHeader(int length, SNISMUXFlags flags) /// /// SNI packet /// The packet with the SMUx header set. - private SNIPacket SetPacketSMUXHeader(SNIPacket packet) + private SniPacket SetPacketSMUXHeader(SniPacket packet) { - Debug.Assert(packet.ReservedHeaderSize == SNISMUXHeader.HEADER_LENGTH, "mars handle attempting to smux packet without smux reservation"); + Debug.Assert(packet.ReservedHeaderSize == SniSmuxHeader.HEADER_LENGTH, "mars handle attempting to smux packet without smux reservation"); - SetupSMUXHeader(packet.Length, SNISMUXFlags.SMUX_DATA); - _currentHeader.Write(packet.GetHeaderBuffer(SNISMUXHeader.HEADER_LENGTH)); + SetupSMUXHeader(packet.Length, SniSmuxFlags.SMUX_DATA); + _currentHeader.Write(packet.GetHeaderBuffer(SniSmuxHeader.HEADER_LENGTH)); packet.SetHeaderActive(); #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, Setting SMUX_DATA header in current header for packet {1}", args0: ConnectionId, args1: packet?._id); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, Setting SMUX_DATA header in current header for packet {1}", args0: ConnectionId, args1: packet?._id); #endif return packet; } @@ -152,10 +154,10 @@ private SNIPacket SetPacketSMUXHeader(SNIPacket packet) /// /// SNI packet /// SNI error code - public override uint Send(SNIPacket packet) + public override uint Send(SniPacket packet) { - Debug.Assert(packet.ReservedHeaderSize == SNISMUXHeader.HEADER_LENGTH, "mars handle attempting to send muxed packet without smux reservation in Send"); - using (TrySNIEventScope.Create(nameof(SNIMarsHandle))) + Debug.Assert(packet.ReservedHeaderSize == SniSmuxHeader.HEADER_LENGTH, "mars handle attempting to send muxed packet without smux reservation in Send"); + using (TrySNIEventScope.Create(nameof(SniMarsHandle))) { while (true) { @@ -167,22 +169,22 @@ public override uint Send(SNIPacket packet) } } - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, Waiting for Acknowledgment event.", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, Waiting for Acknowledgment event.", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater); _ackEvent.Wait(); lock (this) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _sendPacketQueue count found {1}, Acknowledgment event Reset", args0: ConnectionId, args1: _sendPacketQueue?.Count); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _sendPacketQueue count found {1}, Acknowledgment event Reset", args0: ConnectionId, args1: _sendPacketQueue?.Count); _ackEvent.Reset(); } } - SNIPacket muxedPacket = null; + SniPacket muxedPacket = null; lock (this) { muxedPacket = SetPacketSMUXHeader(packet); } - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, SMUX Packet is going to be sent.", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, SMUX Packet is going to be sent.", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater); return _connection.Send(muxedPacket); } } @@ -192,22 +194,22 @@ public override uint Send(SNIPacket packet) /// /// SNI packet /// SNI error code - private uint InternalSendAsync(SNIPacket packet) + private uint InternalSendAsync(SniPacket packet) { - Debug.Assert(packet.ReservedHeaderSize == SNISMUXHeader.HEADER_LENGTH, "mars handle attempting to send muxed packet without smux reservation in InternalSendAsync"); - using (TrySNIEventScope.Create(nameof(SNIMarsHandle))) + Debug.Assert(packet.ReservedHeaderSize == SniSmuxHeader.HEADER_LENGTH, "mars handle attempting to send muxed packet without smux reservation in InternalSendAsync"); + using (TrySNIEventScope.Create(nameof(SniMarsHandle))) { lock (this) { if (_sequenceNumber >= _sendHighwater) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, SNI Queue is full", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, SNI Queue is full", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater); return TdsEnums.SNI_QUEUE_FULL; } - SNIPacket muxedPacket = SetPacketSMUXHeader(packet); + SniPacket muxedPacket = SetPacketSMUXHeader(packet); muxedPacket.SetAsyncIOCompletionCallback(_handleSendCompleteCallback); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, Sending packet", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, Sending packet", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater); return _connection.SendAsync(muxedPacket); } } @@ -219,9 +221,9 @@ private uint InternalSendAsync(SNIPacket packet) /// SNI error code private uint SendPendingPackets() { - using (TrySNIEventScope.Create(nameof(SNIMarsHandle))) + using (TrySNIEventScope.Create(nameof(SniMarsHandle))) { - SNIPacket packet = null; + SniPacket packet = null; while (true) { @@ -236,18 +238,18 @@ private uint SendPendingPackets() if (result != TdsEnums.SNI_SUCCESS && result != TdsEnums.SNI_SUCCESS_IO_PENDING) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.ERR, "MARS Session Id {0}, InternalSendAsync result is not SNI_SUCCESS and is not SNI_SUCCESS_IO_PENDING", args0: ConnectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.ERR, "MARS Session Id {0}, InternalSendAsync result is not SNI_SUCCESS and is not SNI_SUCCESS_IO_PENDING", args0: ConnectionId); return result; } _sendPacketQueue.Dequeue(); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _sendPacketQueue dequeued, count {1}", args0: ConnectionId, args1: _sendPacketQueue?.Count); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _sendPacketQueue dequeued, count {1}", args0: ConnectionId, args1: _sendPacketQueue?.Count); continue; } else { _ackEvent.Set(); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _sendPacketQueue count found {1}, acknowledgment set", args0: ConnectionId, args1: _sendPacketQueue?.Count); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _sendPacketQueue count found {1}, acknowledgment set", args0: ConnectionId, args1: _sendPacketQueue?.Count); } } @@ -264,9 +266,9 @@ private uint SendPendingPackets() /// /// SNI packet /// SNI error code - public override uint SendAsync(SNIPacket packet) + public override uint SendAsync(SniPacket packet) { - using (TrySNIEventScope.Create(nameof(SNIMarsHandle))) + using (TrySNIEventScope.Create(nameof(SniMarsHandle))) { packet.SetAsyncIOCompletionCallback(_handleSendCompleteCallback); lock (this) @@ -275,7 +277,7 @@ public override uint SendAsync(SNIPacket packet) } SendPendingPackets(); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _sendPacketQueue enqueued, count {1}", args0: ConnectionId, args1: _sendPacketQueue?.Count); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _sendPacketQueue enqueued, count {1}", args0: ConnectionId, args1: _sendPacketQueue?.Count); return TdsEnums.SNI_SUCCESS_IO_PENDING; } @@ -286,9 +288,9 @@ public override uint SendAsync(SNIPacket packet) /// /// SNI packet /// SNI error code - public override uint ReceiveAsync(ref SNIPacket packet) + public override uint ReceiveAsync(ref SniPacket packet) { - using (TrySNIEventScope.Create(nameof(SNIMarsHandle))) + using (TrySNIEventScope.Create(nameof(SniMarsHandle))) { lock (_receivedPacketQueue) { @@ -296,14 +298,14 @@ public override uint ReceiveAsync(ref SNIPacket packet) if (_connectionError != null) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.ERR, "MARS Session Id {0}, _asyncReceives {1}, _receiveHighwater {2}, _sendHighwater {3}, _receiveHighwaterLastAck {4}, _connectionError {5}", args0: ConnectionId, args1: _asyncReceives, args2: _receiveHighwater, args3: _sendHighwater, args4: _receiveHighwaterLastAck, args5: _connectionError); - return SNICommon.ReportSNIError(_connectionError); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.ERR, "MARS Session Id {0}, _asyncReceives {1}, _receiveHighwater {2}, _sendHighwater {3}, _receiveHighwaterLastAck {4}, _connectionError {5}", args0: ConnectionId, args1: _asyncReceives, args2: _receiveHighwater, args3: _sendHighwater, args4: _receiveHighwaterLastAck, args5: _connectionError); + return SniCommon.ReportSNIError(_connectionError); } if (queueCount == 0) { _asyncReceives++; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, queueCount 0, _asyncReceives {1}, _receiveHighwater {2}, _sendHighwater {3}, _receiveHighwaterLastAck {4}", args0: ConnectionId, args1: _asyncReceives, args2: _receiveHighwater, args3: _sendHighwater, args4: _receiveHighwaterLastAck); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, queueCount 0, _asyncReceives {1}, _receiveHighwater {2}, _sendHighwater {3}, _receiveHighwaterLastAck {4}", args0: ConnectionId, args1: _asyncReceives, args2: _receiveHighwater, args3: _sendHighwater, args4: _receiveHighwaterLastAck); return TdsEnums.SNI_SUCCESS_IO_PENDING; } @@ -313,7 +315,7 @@ public override uint ReceiveAsync(ref SNIPacket packet) if (queueCount == 1) { #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, packet dequeued {1}, packet Owner {2}, packet refCount {3}, received Packet Queue count {4}", args0: ConnectionId, args1: packet?._id, args2: packet?._owner, args3: packet?._refCount, args4: _receivedPacketQueue?.Count); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, packet dequeued {1}, packet Owner {2}, packet refCount {3}, received Packet Queue count {4}", args0: ConnectionId, args1: packet?._id, args2: packet?._owner, args3: packet?._refCount, args4: _receivedPacketQueue?.Count); #endif _packetEvent.Reset(); } @@ -324,7 +326,7 @@ public override uint ReceiveAsync(ref SNIPacket packet) _receiveHighwater++; } - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _asyncReceives {1}, _receiveHighwater {2}, _sendHighwater {3}, _receiveHighwaterLastAck {4}, queueCount {5}", args0: ConnectionId, args1: _asyncReceives, args2: _receiveHighwater, args3: _sendHighwater, args4: _receiveHighwaterLastAck, args5: _receivedPacketQueue?.Count); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _asyncReceives {1}, _receiveHighwater {2}, _sendHighwater {3}, _receiveHighwaterLastAck {4}, queueCount {5}", args0: ConnectionId, args1: _asyncReceives, args2: _receiveHighwater, args3: _sendHighwater, args4: _receiveHighwaterLastAck, args5: _receivedPacketQueue?.Count); SendAckIfNecessary(); return TdsEnums.SNI_SUCCESS; } @@ -333,9 +335,9 @@ public override uint ReceiveAsync(ref SNIPacket packet) /// /// Handle receive error /// - public void HandleReceiveError(SNIPacket packet) + public void HandleReceiveError(SniPacket packet) { - using (TrySNIEventScope.Create(nameof(SNIMarsHandle))) + using (TrySNIEventScope.Create(nameof(SniMarsHandle))) { // SNIMarsHandle should only receive calls to this function from the SNIMarsConnection aggregator class // which should handle ownership of the packet because the individual mars handles are not aware of @@ -343,8 +345,8 @@ public void HandleReceiveError(SNIPacket packet) lock (_receivedPacketQueue) { - _connectionError = SNILoadHandle.SingletonInstance.LastError; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.ERR, "MARS Session Id {0}, _connectionError to be handled: {1}", args0: ConnectionId, args1: _connectionError); + _connectionError = SniLoadHandle.SingletonInstance.LastError; + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.ERR, "MARS Session Id {0}, _connectionError to be handled: {1}", args0: ConnectionId, args1: _connectionError); _packetEvent.Set(); } @@ -357,9 +359,9 @@ public void HandleReceiveError(SNIPacket packet) /// /// SNI packet /// SNI error code - public void HandleSendComplete(SNIPacket packet, uint sniErrorCode) + public void HandleSendComplete(SniPacket packet, uint sniErrorCode) { - using (TrySNIEventScope.Create(nameof(SNIMarsHandle))) + using (TrySNIEventScope.Create(nameof(SniMarsHandle))) { lock (this) { @@ -369,7 +371,7 @@ public void HandleSendComplete(SNIPacket packet, uint sniErrorCode) } _connection.ReturnPacket(packet); #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, Returned Packet: {1}", args0: ConnectionId, args1: packet?._id); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, Returned Packet: {1}", args0: ConnectionId, args1: packet?._id); #endif } } @@ -380,13 +382,13 @@ public void HandleSendComplete(SNIPacket packet, uint sniErrorCode) /// Send highwater mark public void HandleAck(uint highwater) { - using (TrySNIEventScope.Create(nameof(SNIMarsHandle))) + using (TrySNIEventScope.Create(nameof(SniMarsHandle))) { lock (this) { if (_sendHighwater != highwater) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, Setting _sendHighwater {1} to highwater {2} and send pending packets.", args0: ConnectionId, args1: _sendHighwater, args2: highwater); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, Setting _sendHighwater {1} to highwater {2} and send pending packets.", args0: ConnectionId, args1: _sendHighwater, args2: highwater); _sendHighwater = highwater; SendPendingPackets(); } @@ -399,15 +401,15 @@ public void HandleAck(uint highwater) /// /// SNI packet /// SMUX header - public void HandleReceiveComplete(SNIPacket packet, SNISMUXHeader header) + public void HandleReceiveComplete(SniPacket packet, SniSmuxHeader header) { - using (TrySNIEventScope.Create(nameof(SNIMarsHandle))) + using (TrySNIEventScope.Create(nameof(SniMarsHandle))) { lock (this) { if (_sendHighwater != header.highwater) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, header.highwater {1}, _sendHighwater {2}, Handle Ack with header.highwater", args0: ConnectionId, args1: header?.highwater, args2: _sendHighwater); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, header.highwater {1}, _sendHighwater {2}, Handle Ack with header.highwater", args0: ConnectionId, args1: header?.highwater, args2: _sendHighwater); HandleAck(header.highwater); } @@ -417,13 +419,13 @@ public void HandleReceiveComplete(SNIPacket packet, SNISMUXHeader header) { _receivedPacketQueue.Enqueue(packet); _packetEvent.Set(); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, _receivedPacketQueue count {3}, packet event set", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater, args3: _receivedPacketQueue?.Count); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, _receivedPacketQueue count {3}, packet event set", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater, args3: _receivedPacketQueue?.Count); return; } _asyncReceives--; Debug.Assert(_callbackObject != null); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, _asyncReceives {3}", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater, args3: _asyncReceives); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, _asyncReceives {3}", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater, args3: _asyncReceives); ((TdsParserStateObject)_callbackObject).ReadAsyncCallback(PacketHandle.FromManagedPacket(packet), 0); } @@ -435,7 +437,7 @@ public void HandleReceiveComplete(SNIPacket packet, SNISMUXHeader header) { _receiveHighwater++; } - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _asyncReceives {1}, _receiveHighwater {2}, _sendHighwater {3}, _receiveHighwaterLastAck {4}", args0: ConnectionId, args1: _asyncReceives, args2: _receiveHighwater, args3: _sendHighwater, args4: _receiveHighwaterLastAck); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _asyncReceives {1}, _receiveHighwater {2}, _sendHighwater {3}, _receiveHighwaterLastAck {4}", args0: ConnectionId, args1: _asyncReceives, args2: _receiveHighwater, args3: _sendHighwater, args4: _receiveHighwaterLastAck); SendAckIfNecessary(); } } @@ -456,8 +458,8 @@ private void SendAckIfNecessary() if (receiveHighwater - receiveHighwaterLastAck > ACK_THRESHOLD) { - SendControlPacket(SNISMUXFlags.SMUX_ACK); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _asyncReceives {1}, _receiveHighwater {2}, _sendHighwater {3}, _receiveHighwaterLastAck {4} Sending acknowledgment ACK_THRESHOLD {5}", args0: ConnectionId, args1: _asyncReceives, args2: _receiveHighwater, args3: _sendHighwater, args4: _receiveHighwaterLastAck, args5: ACK_THRESHOLD); + SendControlPacket(SniSmuxFlags.SMUX_ACK); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _asyncReceives {1}, _receiveHighwater {2}, _sendHighwater {3}, _receiveHighwaterLastAck {4} Sending acknowledgment ACK_THRESHOLD {5}", args0: ConnectionId, args1: _asyncReceives, args2: _receiveHighwater, args3: _sendHighwater, args4: _receiveHighwaterLastAck, args5: ACK_THRESHOLD); } } @@ -467,9 +469,9 @@ private void SendAckIfNecessary() /// SNI packet /// Timeout in Milliseconds /// SNI error code - public override uint Receive(out SNIPacket packet, int timeoutInMilliseconds) + public override uint Receive(out SniPacket packet, int timeoutInMilliseconds) { - using (TrySNIEventScope.Create(nameof(SNIMarsHandle))) + using (TrySNIEventScope.Create(nameof(SniMarsHandle))) { packet = null; int queueCount; @@ -481,12 +483,12 @@ public override uint Receive(out SNIPacket packet, int timeoutInMilliseconds) { if (_connectionError != null) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.ERR, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, _connectionError found: {3}.", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater, args3: _connectionError); - return SNICommon.ReportSNIError(_connectionError); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.ERR, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, _connectionError found: {3}.", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater, args3: _connectionError); + return SniCommon.ReportSNIError(_connectionError); } queueCount = _receivedPacketQueue.Count; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, W_receivedPacketQueue count {3}.", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater, args3: queueCount); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, W_receivedPacketQueue count {3}.", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater, args3: queueCount); if (queueCount > 0) { @@ -495,7 +497,7 @@ public override uint Receive(out SNIPacket packet, int timeoutInMilliseconds) if (queueCount == 1) { _packetEvent.Reset(); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, packet event reset, _receivedPacketQueue count 1.", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, packet event reset, _receivedPacketQueue count 1.", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater); } result = TdsEnums.SNI_SUCCESS; @@ -510,15 +512,15 @@ public override uint Receive(out SNIPacket packet, int timeoutInMilliseconds) } SendAckIfNecessary(); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, returning with result {3}.", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater, args3: result); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, returning with result {3}.", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater, args3: result); return result; } - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, Waiting for packet event.", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, Waiting for packet event.", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater); if (!_packetEvent.Wait(timeoutInMilliseconds)) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.SMUX_PROV, 0, SNICommon.ConnTimeoutError, Strings.SNI_ERROR_11); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, _packetEvent wait timed out.", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater); + SniLoadHandle.SingletonInstance.LastError = new SniError(SniProviders.SMUX_PROV, 0, SniCommon.ConnTimeoutError, Strings.SNI_ERROR_11); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniMarsHandle), EventType.INFO, "MARS Session Id {0}, _sequenceNumber {1}, _sendHighwater {2}, _packetEvent wait timed out.", args0: ConnectionId, args1: _sequenceNumber, args2: _sendHighwater); return TdsEnums.SNI_WAIT_TIMEOUT; } } @@ -539,7 +541,7 @@ public override uint CheckConnection() /// /// Receive callback /// Send callback - public override void SetAsyncCallbacks(SNIAsyncCallback receiveCallback, SNIAsyncCallback sendCallback) + public override void SetAsyncCallbacks(SniAsyncCallback receiveCallback, SniAsyncCallback sendCallback) { } @@ -555,12 +557,12 @@ public override void SetBufferSize(int bufferSize) public override void DisableSsl() => _connection.DisableSsl(); - public override SNIPacket RentPacket(int headerSize, int dataSize) => _connection.RentPacket(headerSize, dataSize); + public override SniPacket RentPacket(int headerSize, int dataSize) => _connection.RentPacket(headerSize, dataSize); - public override void ReturnPacket(SNIPacket packet) => _connection.ReturnPacket(packet); + public override void ReturnPacket(SniPacket packet) => _connection.ReturnPacket(packet); -#if DEBUG + #if DEBUG /// /// Test handle for killing underlying connection /// @@ -568,6 +570,8 @@ public override void KillConnection() { _connection.KillConnection(); } -#endif + #endif } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniNetworkStream.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniNetworkStream.netcore.cs new file mode 100644 index 0000000000..f939ba4b7e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniNetworkStream.netcore.cs @@ -0,0 +1,77 @@ +// 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. + +#if NET + +using System; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Data.SqlClient.ManagedSni +{ + /// + /// This class extends NetworkStream to customize stream behavior for Managed SNI implementation. + /// + internal sealed class SniNetworkStream : NetworkStream + { + private readonly ConcurrentQueueSemaphore _writeAsyncSemaphore; + private readonly ConcurrentQueueSemaphore _readAsyncSemaphore; + + public SniNetworkStream(Socket socket, bool ownsSocket) : base(socket, ownsSocket) + { + _writeAsyncSemaphore = new ConcurrentQueueSemaphore(1); + _readAsyncSemaphore = new ConcurrentQueueSemaphore(1); + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); + } + + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + await _readAsyncSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + return await base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniSslStream), EventType.ERR, "Internal Exception occurred while reading data: {0}", args0: e?.Message); + throw; + } + finally + { + _readAsyncSemaphore.Release(); + } + } + + // Prevent the WriteAsync collisions by running the task in a Semaphore Slim + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return WriteAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); + } + + public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + await _writeAsyncSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + await base.WriteAsync(buffer, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniSslStream), EventType.ERR, "Internal Exception occurred while reading data: {0}", args0: e?.Message); + throw; + } + finally + { + _writeAsyncSemaphore.Release(); + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniNpHandle.netcore.cs similarity index 80% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniNpHandle.netcore.cs index 8f8af57f58..7a1c2ec660 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniNpHandle.netcore.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; using System.ComponentModel; using System.IO; @@ -12,12 +14,12 @@ using System.Threading; using Microsoft.Data.ProviderBase; -namespace Microsoft.Data.SqlClient.SNI +namespace Microsoft.Data.SqlClient.ManagedSni { /// /// Named Pipe connection handle /// - internal sealed class SNINpHandle : SNIPhysicalHandle + internal sealed class SniNpHandle : SniPhysicalHandle { internal const string DefaultPipePath = @"sql\query"; // e.g. \\HOSTNAME\pipe\sql\query // private const int MAX_PIPE_INSTANCES = 255; // TODO: Investigate pipe instance limit. @@ -32,19 +34,19 @@ internal sealed class SNINpHandle : SNIPhysicalHandle private SslOverTdsStream _sslOverTdsStream; private SslStream _sslStream; - private SNIAsyncCallback _receiveCallback; - private SNIAsyncCallback _sendCallback; + private SniAsyncCallback _receiveCallback; + private SniAsyncCallback _sendCallback; private bool _validateCert = true; private readonly uint _status = TdsEnums.SNI_UNINITIALIZED; private int _bufferSize = TdsEnums.DEFAULT_LOGIN_PACKET_SIZE; private readonly Guid _connectionId = Guid.NewGuid(); - public SNINpHandle(string serverName, string pipeName, TimeoutTimer timeout, bool tlsFirst, string hostNameInCertificate, string serverCertificateFilename) + public SniNpHandle(string serverName, string pipeName, TimeoutTimer timeout, bool tlsFirst, string hostNameInCertificate, string serverCertificateFilename) { - using (TrySNIEventScope.Create(nameof(SNINpHandle))) + using (TrySNIEventScope.Create(nameof(SniNpHandle))) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, "Connection Id {0}, Setting server name = {1}, pipe name = {2}", args0: _connectionId, args1: serverName, args2: pipeName); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.INFO, "Connection Id {0}, Setting server name = {1}, pipe name = {2}", args0: _connectionId, args1: serverName, args2: pipeName); _sendSync = new object(); _targetServer = serverName; @@ -61,7 +63,7 @@ public SNINpHandle(string serverName, string pipeName, TimeoutTimer timeout, boo if (timeout.IsInfinite) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.INFO, "Connection Id {0}, Setting server name = {1}, pipe name = {2}. Connecting with infinite timeout.", args0: _connectionId, args1: serverName, @@ -71,7 +73,7 @@ public SNINpHandle(string serverName, string pipeName, TimeoutTimer timeout, boo else { int timeoutMilliseconds = timeout.MillisecondsRemainingInt; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.INFO, "Connection Id {0}, Setting server name = {1}, pipe name = {2}. Connecting within the {3} specified milliseconds.", args0: _connectionId, args1: serverName, @@ -82,24 +84,24 @@ public SNINpHandle(string serverName, string pipeName, TimeoutTimer timeout, boo } catch (TimeoutException te) { - SNICommon.ReportSNIError(SNIProviders.NP_PROV, SNICommon.ConnOpenFailedError, te); + SniCommon.ReportSNIError(SniProviders.NP_PROV, SniCommon.ConnOpenFailedError, te); _status = TdsEnums.SNI_ERROR; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.ERR, "Connection Id {0}, Connection Timed out. Error Code 1 Exception = {1}", args0: _connectionId, args1: te?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.ERR, "Connection Id {0}, Connection Timed out. Error Code 1 Exception = {1}", args0: _connectionId, args1: te?.Message); return; } catch (IOException ioe) { - SNICommon.ReportSNIError(SNIProviders.NP_PROV, SNICommon.ConnOpenFailedError, ioe); + SniCommon.ReportSNIError(SniProviders.NP_PROV, SniCommon.ConnOpenFailedError, ioe); _status = TdsEnums.SNI_ERROR; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.ERR, "Connection Id {0}, IO Exception occurred. Error Code 1 Exception = {1}", args0: _connectionId, args1: ioe?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.ERR, "Connection Id {0}, IO Exception occurred. Error Code 1 Exception = {1}", args0: _connectionId, args1: ioe?.Message); return; } if (!_pipeStream.IsConnected || !_pipeStream.CanWrite || !_pipeStream.CanRead) { - SNICommon.ReportSNIError(SNIProviders.NP_PROV, 0, SNICommon.ConnOpenFailedError, Strings.SNI_ERROR_40); + SniCommon.ReportSNIError(SniProviders.NP_PROV, 0, SniCommon.ConnOpenFailedError, Strings.SNI_ERROR_40); _status = TdsEnums.SNI_ERROR; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.ERR, "Connection Id {0}, Pipe Stream not operational. Error Code 1 Exception = {1}", args0: _connectionId, args1: Strings.SNI_ERROR_1); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.ERR, "Connection Id {0}, Pipe Stream not operational. Error Code 1 Exception = {1}", args0: _connectionId, args1: Strings.SNI_ERROR_1); return; } @@ -110,7 +112,7 @@ public SNINpHandle(string serverName, string pipeName, TimeoutTimer timeout, boo _sslOverTdsStream = new SslOverTdsStream(_pipeStream, _connectionId); stream = _sslOverTdsStream; } - _sslStream = new SNISslStream(stream, true, new RemoteCertificateValidationCallback(ValidateServerCertificate)); + _sslStream = new SniSslStream(stream, true, new RemoteCertificateValidationCallback(ValidateServerCertificate)); _stream = _pipeStream; _status = TdsEnums.SNI_SUCCESS; @@ -138,16 +140,16 @@ public override int ProtocolVersion public override uint CheckConnection() { - using (TrySNIEventScope.Create(nameof(SNINpHandle))) + using (TrySNIEventScope.Create(nameof(SniNpHandle))) { if (!_stream.CanWrite || !_stream.CanRead) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.ERR, "Connection Id {0}, Cannot write or read to/from the stream", args0: _connectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.ERR, "Connection Id {0}, Cannot write or read to/from the stream", args0: _connectionId); return TdsEnums.SNI_ERROR; } else { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, "Connection Id {0}, Can read and write to/from stream.", args0: _connectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.INFO, "Connection Id {0}, Can read and write to/from stream.", args0: _connectionId); return TdsEnums.SNI_SUCCESS; } } @@ -177,15 +179,15 @@ public override void Dispose() //Release any references held by _stream. _stream = null; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, "Connection Id {0}, All streams disposed and references cleared.", args0: _connectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.INFO, "Connection Id {0}, All streams disposed and references cleared.", args0: _connectionId); } } - public override uint Receive(out SNIPacket packet, int timeout) + public override uint Receive(out SniPacket packet, int timeout) { - using (TrySNIEventScope.Create(nameof(SNINpHandle))) + using (TrySNIEventScope.Create(nameof(SniNpHandle))) { - SNIPacket errorPacket; + SniPacket errorPacket; lock (this) { packet = null; @@ -193,29 +195,29 @@ public override uint Receive(out SNIPacket packet, int timeout) { packet = RentPacket(headerSize: 0, dataSize: _bufferSize); packet.ReadFromStream(_stream); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, "Connection Id {0}, Rented and read packet, dataLeft {1}", args0: _connectionId, args1: packet?.DataLeft); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.INFO, "Connection Id {0}, Rented and read packet, dataLeft {1}", args0: _connectionId, args1: packet?.DataLeft); if (packet.Length == 0) { errorPacket = packet; packet = null; var e = new Win32Exception(); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.ERR, "Connection Id {0}, Packet length found 0, Win32 exception raised: {1}", args0: _connectionId, args1: e?.Message); - return ReportErrorAndReleasePacket(errorPacket, (uint)e.NativeErrorCode, 0, e.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.ERR, "Connection Id {0}, Packet length found 0, Win32 exception raised: {1}", args0: _connectionId, args1: e?.Message); + return ReportErrorAndReleasePacket(errorPacket, e.NativeErrorCode, 0, e.Message); } } catch (ObjectDisposedException ode) { errorPacket = packet; packet = null; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.ERR, "Connection Id {0}, ObjectDisposedException occurred: {1}.", args0: _connectionId, args1: ode?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.ERR, "Connection Id {0}, ObjectDisposedException occurred: {1}.", args0: _connectionId, args1: ode?.Message); return ReportErrorAndReleasePacket(errorPacket, ode); } catch (IOException ioe) { errorPacket = packet; packet = null; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.ERR, "Connection Id {0}, IOException occurred: {1}.", args0: _connectionId, args1: ioe?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.ERR, "Connection Id {0}, IOException occurred: {1}.", args0: _connectionId, args1: ioe?.Message); return ReportErrorAndReleasePacket(errorPacket, ioe); } return TdsEnums.SNI_SUCCESS; @@ -223,39 +225,39 @@ public override uint Receive(out SNIPacket packet, int timeout) } } - public override uint ReceiveAsync(ref SNIPacket packet) + public override uint ReceiveAsync(ref SniPacket packet) { - using (TrySNIEventScope.Create(nameof(SNINpHandle))) + using (TrySNIEventScope.Create(nameof(SniNpHandle))) { - SNIPacket errorPacket; + SniPacket errorPacket; packet = RentPacket(headerSize: 0, dataSize: _bufferSize); packet.SetAsyncIOCompletionCallback(_receiveCallback); try { packet.ReadFromStreamAsync(_stream); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, "Connection Id {0}, Rented and read packet asynchronously, dataLeft {1}", args0: _connectionId, args1: packet?.DataLeft); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.INFO, "Connection Id {0}, Rented and read packet asynchronously, dataLeft {1}", args0: _connectionId, args1: packet?.DataLeft); return TdsEnums.SNI_SUCCESS_IO_PENDING; } catch (ObjectDisposedException ode) { errorPacket = packet; packet = null; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.ERR, "Connection Id {0}, ObjectDisposedException occurred: {1}.", args0: _connectionId, args1: ode?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.ERR, "Connection Id {0}, ObjectDisposedException occurred: {1}.", args0: _connectionId, args1: ode?.Message); return ReportErrorAndReleasePacket(errorPacket, ode); } catch (IOException ioe) { errorPacket = packet; packet = null; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.ERR, "Connection Id {0}, IOException occurred: {1}.", args0: _connectionId, args1: ioe?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.ERR, "Connection Id {0}, IOException occurred: {1}.", args0: _connectionId, args1: ioe?.Message); return ReportErrorAndReleasePacket(errorPacket, ioe); } } } - public override uint Send(SNIPacket packet) + public override uint Send(SniPacket packet) { - using (TrySNIEventScope.Create(nameof(SNINpHandle))) + using (TrySNIEventScope.Create(nameof(SniNpHandle))) { bool releaseLock = false; try @@ -281,18 +283,18 @@ public override uint Send(SNIPacket packet) { try { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, "Connection Id {0}, Packet writing to stream, dataLeft {1}", args0: _connectionId, args1: packet?.DataLeft); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.INFO, "Connection Id {0}, Packet writing to stream, dataLeft {1}", args0: _connectionId, args1: packet?.DataLeft); packet.WriteToStream(_stream); return TdsEnums.SNI_SUCCESS; } catch (ObjectDisposedException ode) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.ERR, "Connection Id {0}, ObjectDisposedException occurred: {1}.", args0: _connectionId, args1: ode?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.ERR, "Connection Id {0}, ObjectDisposedException occurred: {1}.", args0: _connectionId, args1: ode?.Message); return ReportErrorAndReleasePacket(packet, ode); } catch (IOException ioe) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.ERR, "Connection Id {0}, IOException occurred: {1}.", args0: _connectionId, args1: ioe?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.ERR, "Connection Id {0}, IOException occurred: {1}.", args0: _connectionId, args1: ioe?.Message); return ReportErrorAndReleasePacket(packet, ioe); } } @@ -307,17 +309,17 @@ public override uint Send(SNIPacket packet) } } - public override uint SendAsync(SNIPacket packet) + public override uint SendAsync(SniPacket packet) { - using (TrySNIEventScope.Create(nameof(SNINpHandle))) + using (TrySNIEventScope.Create(nameof(SniNpHandle))) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, "Connection Id {0}, Packet writing to stream, dataLeft {1}", args0: _connectionId, args1: packet?.DataLeft); - packet.WriteToStreamAsync(_stream, _sendCallback, SNIProviders.NP_PROV); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.INFO, "Connection Id {0}, Packet writing to stream, dataLeft {1}", args0: _connectionId, args1: packet?.DataLeft); + packet.WriteToStreamAsync(_stream, _sendCallback, SniProviders.NP_PROV); return TdsEnums.SNI_SUCCESS_IO_PENDING; } } - public override void SetAsyncCallbacks(SNIAsyncCallback receiveCallback, SNIAsyncCallback sendCallback) + public override void SetAsyncCallbacks(SniAsyncCallback receiveCallback, SniAsyncCallback sendCallback) { _receiveCallback = receiveCallback; _sendCallback = sendCallback; @@ -325,7 +327,7 @@ public override void SetAsyncCallbacks(SNIAsyncCallback receiveCallback, SNIAsyn public override uint EnableSsl(uint options) { - using (TrySNIEventScope.Create(nameof(SNINpHandle))) + using (TrySNIEventScope.Create(nameof(SniNpHandle))) { _validateCert = (options & TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE) != 0; try @@ -346,13 +348,13 @@ public override uint EnableSsl(uint options) } catch (AuthenticationException aue) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.ERR, "Connection Id {0}, AuthenticationException message = {1}.", args0: ConnectionId, args1: aue?.Message); - return SNICommon.ReportSNIError(SNIProviders.NP_PROV, SNICommon.InternalExceptionError, aue); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.ERR, "Connection Id {0}, AuthenticationException message = {1}.", args0: ConnectionId, args1: aue?.Message); + return SniCommon.ReportSNIError(SniProviders.NP_PROV, SniCommon.InternalExceptionError, aue); } catch (InvalidOperationException ioe) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.ERR, "Connection Id {0}, InvalidOperationException message = {1}.", args0: ConnectionId, args1: ioe?.Message); - return SNICommon.ReportSNIError(SNIProviders.NP_PROV, SNICommon.InternalExceptionError, ioe); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.ERR, "Connection Id {0}, InvalidOperationException message = {1}.", args0: ConnectionId, args1: ioe?.Message); + return SniCommon.ReportSNIError(SniProviders.NP_PROV, SniCommon.InternalExceptionError, ioe); } _stream = _sslStream; return TdsEnums.SNI_SUCCESS; @@ -379,16 +381,16 @@ public override void DisableSsl() /// true if valid private bool ValidateServerCertificate(object sender, X509Certificate serverCertificate, X509Chain chain, SslPolicyErrors policyErrors) { - using (TrySNIEventScope.Create(nameof(SNINpHandle))) + using (TrySNIEventScope.Create(nameof(SniNpHandle))) { if (!_validateCert) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, "Connection Id {0}, Certificate validation not requested.", args0: ConnectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.INFO, "Connection Id {0}, Certificate validation not requested.", args0: ConnectionId); return true; } - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, "Connection Id {0}, Proceeding to SSL certificate validation.", args0: ConnectionId); - return SNICommon.ValidateSslServerCertificate(_connectionId, _targetServer, _hostNameInCertificate, serverCertificate, _serverCertificateFilename, policyErrors); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.INFO, "Connection Id {0}, Proceeding to SSL certificate validation.", args0: ConnectionId); + return SniCommon.ValidateSslServerCertificate(_connectionId, _targetServer, _hostNameInCertificate, serverCertificate, _serverCertificateFilename, policyErrors); } } @@ -401,27 +403,27 @@ public override void SetBufferSize(int bufferSize) _bufferSize = bufferSize; } - private uint ReportErrorAndReleasePacket(SNIPacket packet, Exception sniException) + private uint ReportErrorAndReleasePacket(SniPacket packet, Exception sniException) { if (packet != null) { ReturnPacket(packet); } - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, "Connection Id {0}, Packet returned, error occurred: {1}", args0: ConnectionId, args1: sniException?.Message); - return SNICommon.ReportSNIError(SNIProviders.NP_PROV, SNICommon.InternalExceptionError, sniException); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.INFO, "Connection Id {0}, Packet returned, error occurred: {1}", args0: ConnectionId, args1: sniException?.Message); + return SniCommon.ReportSNIError(SniProviders.NP_PROV, SniCommon.InternalExceptionError, sniException); } - private uint ReportErrorAndReleasePacket(SNIPacket packet, uint nativeError, uint sniError, string errorMessage) + private uint ReportErrorAndReleasePacket(SniPacket packet, int nativeError, uint sniError, string errorMessage) { if (packet != null) { ReturnPacket(packet); } - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNINpHandle), EventType.INFO, "Connection Id {0}, Packet returned, error occurred: {1}", args0: ConnectionId, args1: errorMessage); - return SNICommon.ReportSNIError(SNIProviders.NP_PROV, nativeError, sniError, errorMessage); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniNpHandle), EventType.INFO, "Connection Id {0}, Packet returned, error occurred: {1}", args0: ConnectionId, args1: errorMessage); + return SniCommon.ReportSNIError(SniProviders.NP_PROV, nativeError, sniError, errorMessage); } -#if DEBUG + #if DEBUG /// /// Test handle for killing underlying connection /// @@ -430,6 +432,8 @@ public override void KillConnection() _pipeStream.Dispose(); _pipeStream = null; } -#endif + #endif } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIPacket.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniPacket.netcore.cs similarity index 87% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIPacket.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniPacket.netcore.cs index fd542e542f..5270513c05 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIPacket.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniPacket.netcore.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET // #define TRACE_HISTORY // this is used for advanced debugging when you need to trace the entire lifetime of a single packet, be very careful with it using System; @@ -11,12 +12,12 @@ using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Data.SqlClient.SNI +namespace Microsoft.Data.SqlClient.ManagedSni { /// /// SNI Packet /// - internal sealed class SNIPacket + internal sealed class SniPacket { private static readonly Action, object> s_readCallback = ReadFromStreamAsyncContinuation; private int _dataLength; // the length of the data in the data segment, advanced by Append-ing data, does not include smux header length @@ -25,14 +26,14 @@ internal sealed class SNIPacket private int _headerLength; // the amount of space at the start of the array reserved for the smux header, this is zeroed in SetHeader // _headerOffset is not needed because it is always 0 private byte[] _data; - private SNIAsyncCallback _asyncIOCompletionCallback; + private SniAsyncCallback _asyncIOCompletionCallback; #if DEBUG internal readonly int _id; // in debug mode every packet is assigned a unique id so that the entire lifetime can be tracked when debugging /// refcount = 0 means that a packet should only exist in the pool /// refcount = 1 means that a packet is active /// refcount > 1 means that a packet has been reused in some way and is a serious error internal int _refCount; - internal readonly SNIHandle _owner; // used in debug builds to check that packets are being returned to the correct pool + internal readonly SniHandle _owner; // used in debug builds to check that packets are being returned to the correct pool internal string _traceTag; // used in debug builds to assist tracing what steps the packet has been through #if TRACE_HISTORY @@ -59,7 +60,7 @@ public enum Direction /// public bool IsActive => _refCount == 1; - public SNIPacket(SNIHandle owner, int id) + public SniPacket(SniHandle owner, int id) : this() { #if TRACE_HISTORY @@ -67,22 +68,22 @@ public SNIPacket(SNIHandle owner, int id) #endif _id = id; _owner = owner; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} instantiated,", args0: _owner?.ConnectionId, args1: _id); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} instantiated,", args0: _owner?.ConnectionId, args1: _id); } // the finalizer is only included in debug builds and is used to ensure that all packets are correctly recycled // it is not an error if a packet is dropped but it is undesirable so all efforts should be made to make sure we // do not drop them for the GC to pick up - ~SNIPacket() + ~SniPacket() { if (_data != null) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.ERR, "Finalizer called for unreleased SNIPacket, Connection Id {0}, Packet Id {1}, _refCount {2}, DataLeft {3}, tag {4}", args0: _owner?.ConnectionId, args1: _id, args2: _refCount, args3: DataLeft, args4: _traceTag); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniPacket), EventType.ERR, "Finalizer called for unreleased SNIPacket, Connection Id {0}, Packet Id {1}, _refCount {2}, DataLeft {3}, tag {4}", args0: _owner?.ConnectionId, args1: _id, args2: _refCount, args3: DataLeft, args4: _traceTag); } } #endif - public SNIPacket() + public SniPacket() { } @@ -114,7 +115,7 @@ public SNIPacket() /// Set async receive callback /// /// Completion callback - public void SetAsyncIOCompletionCallback(SNIAsyncCallback asyncIOCompletionCallback) => _asyncIOCompletionCallback = asyncIOCompletionCallback; + public void SetAsyncIOCompletionCallback(SniAsyncCallback asyncIOCompletionCallback) => _asyncIOCompletionCallback = asyncIOCompletionCallback; /// /// Invoke the receive callback @@ -135,7 +136,7 @@ public void Allocate(int headerLength, int dataLength) _dataOffset = 0; _headerLength = headerLength; #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} allocated with _headerLength {2}, _dataCapacity {3}", args0: _owner?.ConnectionId, args1: _id, args2: _headerLength, args3: _dataCapacity); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} allocated with _headerLength {2}, _dataCapacity {3}", args0: _owner?.ConnectionId, args1: _id, args2: _headerLength, args3: _dataCapacity); #endif } @@ -156,12 +157,12 @@ public void GetData(byte[] buffer, ref int dataSize) /// Packet /// Data to take /// Amount of data taken - public int TakeData(SNIPacket packet, int size) + public int TakeData(SniPacket packet, int size) { int dataSize = TakeData(packet._data, packet._headerLength + packet._dataLength, size); packet._dataLength += dataSize; #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} took data from Packet Id {2} dataSize {3}, _dataLength {4}", args0: _owner?.ConnectionId, args1: _id, args2: packet?._id, args3: dataSize, args4: packet._dataLength); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} took data from Packet Id {2} dataSize {3}, _dataLength {4}", args0: _owner?.ConnectionId, args1: _id, args2: packet?._id, args3: dataSize, args4: packet._dataLength); #endif return dataSize; } @@ -176,7 +177,7 @@ public void AppendData(byte[] data, int size) Buffer.BlockCopy(data, 0, _data, _headerLength + _dataLength, size); _dataLength += size; #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} was appended with size {2}, _dataLength {3}", args0: _owner?.ConnectionId, args1: _id, args2: size, args3: _dataLength); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} was appended with size {2}, _dataLength {3}", args0: _owner?.ConnectionId, args1: _id, args2: size, args3: _dataLength); #endif } @@ -202,7 +203,7 @@ public int TakeData(byte[] buffer, int dataOffset, int size) Buffer.BlockCopy(_data, _headerLength + _dataOffset, buffer, dataOffset, size); _dataOffset += size; #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} took data size {2}, _dataLength {3}, _dataOffset {4}", args0: _owner?.ConnectionId, args1: _id, args2: size, args3: _dataLength, args4: _dataOffset); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} took data size {2}, _dataLength {3}, _dataOffset {4}", args0: _owner?.ConnectionId, args1: _id, args2: size, args3: _dataLength, args4: _dataOffset); #endif return size; } @@ -222,7 +223,7 @@ public void SetHeaderActive() _dataLength += _headerLength; _headerLength = 0; #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} _dataLength {2} header set to active.", args0: _owner?.ConnectionId, args1: _id, args2: _dataLength); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} _dataLength {2} header set to active.", args0: _owner?.ConnectionId, args1: _id, args2: _dataLength); #endif } @@ -240,7 +241,7 @@ public void Release() _dataCapacity = 0; } #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} _headerLength {2} and _dataLength {3} released.", args0: _owner?.ConnectionId, args1: _id, args2: _headerLength, args3: _dataLength); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} _headerLength {2} and _dataLength {3} released.", args0: _owner?.ConnectionId, args1: _id, args2: _headerLength, args3: _dataLength); #endif _dataLength = 0; _dataOffset = 0; @@ -257,7 +258,7 @@ public void ReadFromStream(Stream stream) { _dataLength = stream.Read(_data, _headerLength, _dataCapacity); #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} _dataLength {2} read from stream.", args0: _owner?.ConnectionId, args1: _id, args2: _dataLength); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} _dataLength {2} read from stream.", args0: _owner?.ConnectionId, args1: _id, args2: _dataLength); #endif } @@ -279,14 +280,14 @@ public void ReadFromStreamAsync(Stream stream) private static void ReadFromStreamAsyncContinuation(Task task, object state) { - SNIPacket packet = (SNIPacket)state; + SniPacket packet = (SniPacket)state; bool error = false; Exception e = task.Exception?.InnerException; if (e != null) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.TCP_PROV, SNICommon.InternalExceptionError, e); + SniLoadHandle.SingletonInstance.LastError = new SniError(SniProviders.TCP_PROV, SniCommon.InternalExceptionError, e); #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.ERR, "Connection Id {0}, Internal Exception occurred while reading data: {1}", args0: packet._owner?.ConnectionId, args1: e?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniPacket), EventType.ERR, "Connection Id {0}, Internal Exception occurred while reading data: {1}", args0: packet._owner?.ConnectionId, args1: e?.Message); #endif error = true; } @@ -294,13 +295,13 @@ private static void ReadFromStreamAsyncContinuation(Task task, object state { packet._dataLength = task.Result; #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} _dataLength {2} read from stream.", args0: packet._owner?.ConnectionId, args1: packet._id, args2: packet._dataLength); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} _dataLength {2} read from stream.", args0: packet._owner?.ConnectionId, args1: packet._id, args2: packet._dataLength); #endif if (packet._dataLength == 0) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.TCP_PROV, 0, SNICommon.ConnTerminatedError, Strings.SNI_ERROR_2); + SniLoadHandle.SingletonInstance.LastError = new SniError(SniProviders.TCP_PROV, 0, SniCommon.ConnTerminatedError, Strings.SNI_ERROR_2); #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.ERR, "Connection Id {0}, No data read from stream, connection was terminated.", args0: packet._owner?.ConnectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniPacket), EventType.ERR, "Connection Id {0}, No data read from stream, connection was terminated.", args0: packet._owner?.ConnectionId); #endif error = true; } @@ -317,7 +318,7 @@ public void WriteToStream(Stream stream) { stream.Write(_data, _headerLength, _dataLength); #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} _dataLength {2} written to stream.", args0: _owner?.ConnectionId, args1: _id, args2: _dataLength); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} _dataLength {2} written to stream.", args0: _owner?.ConnectionId, args1: _id, args2: _dataLength); #endif } @@ -327,21 +328,21 @@ public void WriteToStream(Stream stream) /// Stream to write to /// SNI Asynchronous Callback /// SNI provider identifier - public async void WriteToStreamAsync(Stream stream, SNIAsyncCallback callback, SNIProviders provider) + public async void WriteToStreamAsync(Stream stream, SniAsyncCallback callback, SniProviders provider) { uint status = TdsEnums.SNI_SUCCESS; try { await stream.WriteAsync(_data, 0, _dataLength, CancellationToken.None).ConfigureAwait(false); #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} _dataLength {2} written to stream.", args0: _owner?.ConnectionId, args1: _id, args2: _dataLength); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniPacket), EventType.INFO, "Connection Id {0}, Packet Id {1} _dataLength {2} written to stream.", args0: _owner?.ConnectionId, args1: _id, args2: _dataLength); #endif } catch (Exception e) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(provider, SNICommon.InternalExceptionError, e); + SniLoadHandle.SingletonInstance.LastError = new SniError(provider, SniCommon.InternalExceptionError, e); #if DEBUG - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIPacket), EventType.ERR, "Connection Id {0}, Internal Exception occurred while writing data: {1}", args0: _owner?.ConnectionId, args1: e?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniPacket), EventType.ERR, "Connection Id {0}, Internal Exception occurred while writing data: {1}", args0: _owner?.ConnectionId, args1: e?.Message); #endif status = TdsEnums.SNI_ERROR; } @@ -349,3 +350,5 @@ public async void WriteToStreamAsync(Stream stream, SNIAsyncCallback callback, S } } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIPhysicalHandle.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniPhysicalHandle.netcore.cs similarity index 84% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIPhysicalHandle.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniPhysicalHandle.netcore.cs index 3677abe6cd..3ee2111e78 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIPhysicalHandle.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniPhysicalHandle.netcore.cs @@ -2,37 +2,40 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; +using Microsoft.Data.SqlClient.Utilities; -namespace Microsoft.Data.SqlClient.SNI +namespace Microsoft.Data.SqlClient.ManagedSni { - internal abstract class SNIPhysicalHandle : SNIHandle + internal abstract class SniPhysicalHandle : SniHandle { protected const int DefaultPoolSize = 4; #if DEBUG private static int s_packetId; #endif - private SqlObjectPool _pool; + private ObjectPool _pool; - protected SNIPhysicalHandle(int poolSize = DefaultPoolSize) + protected SniPhysicalHandle(int poolSize = DefaultPoolSize) { - _pool = new SqlObjectPool(poolSize); + _pool = new ObjectPool(poolSize); } - public override SNIPacket RentPacket(int headerSize, int dataSize) + public override SniPacket RentPacket(int headerSize, int dataSize) { - SNIPacket packet; + SniPacket packet; if (!_pool.TryGet(out packet)) { #if DEBUG int id = Interlocked.Increment(ref s_packetId); - packet = new SNIPacket(this, id); + packet = new SniPacket(this, id); #else - packet = new SNIPacket(); + packet = new SniPacket(); #endif } #if DEBUG @@ -56,7 +59,7 @@ public override SNIPacket RentPacket(int headerSize, int dataSize) return packet; } - public override void ReturnPacket(SNIPacket packet) + public override void ReturnPacket(SniPacket packet) { #if DEBUG Debug.Assert(packet != null, "releasing null SNIPacket"); @@ -97,3 +100,5 @@ private string GetStackParts() #endif } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProviders.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProviders.netcore.cs new file mode 100644 index 0000000000..2c040a11cf --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProviders.netcore.cs @@ -0,0 +1,30 @@ +// 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. + +#if NET + +namespace Microsoft.Data.SqlClient.ManagedSni +{ + /// + /// SNI provider identifiers + /// + internal enum SniProviders + { + // @TODO: Rename to follow enum naming conventions, remove PROV suffix - we get it, it's in the Providers enum. + HTTP_PROV = 0, // HTTP Provider + NP_PROV = 1, // Named Pipes Provider + SESSION_PROV = 2, // Session Provider + SIGN_PROV = 3, // Sign Provider + SM_PROV = 4, // Shared Memory Provider + SMUX_PROV = 5, // SMUX Provider + SSL_PROV = 6, // SSL Provider + TCP_PROV = 7, // TCP Provider + VIA_PROV = 8, // Virtual Interface Architecture Provider + CTAIP_PROV = 9, + MAX_PROVS = 10, // Number of providers + INVALID_PROV = 11 // SQL Network Interfaces + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs similarity index 87% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs index 463283cc73..817ad1f4f2 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniProxy.netcore.cs @@ -2,30 +2,30 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; -using System.Buffers; using System.Diagnostics; using System.IO; using System.Net; -using System.Net.Security; using System.Net.Sockets; using System.Text; using Microsoft.Data.ProviderBase; -namespace Microsoft.Data.SqlClient.SNI +namespace Microsoft.Data.SqlClient.ManagedSni { /// /// Managed SNI proxy implementation. Contains many SNI entry points used by SqlClient. /// - internal class SNIProxy + internal class SniProxy { private const int DefaultSqlServerPort = 1433; private const int DefaultSqlServerDacPort = 1434; private const string SqlServerSpnHeader = "MSSQLSvc"; - private static readonly SNIProxy s_singleton = new SNIProxy(); + private static readonly SniProxy s_singleton = new SniProxy(); - internal static SNIProxy Instance => s_singleton; + internal static SniProxy Instance => s_singleton; /// /// Create a SNI connection handle @@ -33,7 +33,7 @@ internal class SNIProxy /// Full server name from connection string /// Timer expiration /// Instance name - /// SPN + /// SPN /// pre-defined SPN /// Flush packet cache /// Asynchronous connection @@ -46,11 +46,11 @@ internal class SNIProxy /// Used for the HostName in certificate /// Used for the path to the Server Certificate /// SNI handle - internal static SNIHandle CreateConnectionHandle( + internal static SniHandle CreateConnectionHandle( string fullServerName, TimeoutTimer timeout, out byte[] instanceName, - ref byte[][] spnBuffer, + out ResolvedServerSpn resolvedSpn, string serverSPN, bool flushCache, bool async, @@ -64,6 +64,7 @@ internal static SNIHandle CreateConnectionHandle( string serverCertificateFilename) { instanceName = new byte[1]; + resolvedSpn = default; bool errorWithLocalDBProcessing; string localDBDataSource = GetLocalDBDataSource(fullServerName, out errorWithLocalDBProcessing); @@ -81,7 +82,7 @@ internal static SNIHandle CreateConnectionHandle( return null; } - SNIHandle sniHandle = null; + SniHandle sniHandle = null; switch (details.ResolvedProtocol) { case DataSource.Protocol.Admin: @@ -102,11 +103,11 @@ internal static SNIHandle CreateConnectionHandle( { try { - spnBuffer = GetSqlServerSPNs(details, serverSPN); + resolvedSpn = GetSqlServerSPNs(details, serverSPN); } catch (Exception e) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, SNICommon.ErrorSpnLookup, e); + SniLoadHandle.SingletonInstance.LastError = new SniError(SniProviders.INVALID_PROV, SniCommon.ErrorSpnLookup, e); } } @@ -114,12 +115,12 @@ internal static SNIHandle CreateConnectionHandle( return sniHandle; } - private static byte[][] GetSqlServerSPNs(DataSource dataSource, string serverSPN) + private static ResolvedServerSpn GetSqlServerSPNs(DataSource dataSource, string serverSPN) { Debug.Assert(!string.IsNullOrWhiteSpace(dataSource.ServerName)); if (!string.IsNullOrWhiteSpace(serverSPN)) { - return new byte[1][] { Encoding.Unicode.GetBytes(serverSPN) }; + return new(serverSPN); } string hostName = dataSource.ServerName; @@ -137,7 +138,7 @@ private static byte[][] GetSqlServerSPNs(DataSource dataSource, string serverSPN return GetSqlServerSPNs(hostName, postfix, dataSource.ResolvedProtocol); } - private static byte[][] GetSqlServerSPNs(string hostNameOrAddress, string portOrInstanceName, DataSource.Protocol protocol) + private static ResolvedServerSpn GetSqlServerSPNs(string hostNameOrAddress, string portOrInstanceName, DataSource.Protocol protocol) { Debug.Assert(!string.IsNullOrWhiteSpace(hostNameOrAddress)); IPHostEntry hostEntry = null; @@ -168,12 +169,12 @@ private static byte[][] GetSqlServerSPNs(string hostNameOrAddress, string portOr string serverSpnWithDefaultPort = serverSpn + $":{DefaultSqlServerPort}"; // Set both SPNs with and without Port as Port is optional for default instance SqlClientEventSource.Log.TryAdvancedTraceEvent("SNIProxy.GetSqlServerSPN | Info | ServerSPNs {0} and {1}", serverSpn, serverSpnWithDefaultPort); - return new byte[][] { Encoding.Unicode.GetBytes(serverSpn), Encoding.Unicode.GetBytes(serverSpnWithDefaultPort) }; + return new(serverSpn, serverSpnWithDefaultPort); } // else Named Pipes do not need to valid port SqlClientEventSource.Log.TryAdvancedTraceEvent("SNIProxy.GetSqlServerSPN | Info | ServerSPN {0}", serverSpn); - return new byte[][] { Encoding.Unicode.GetBytes(serverSpn) }; + return new(serverSpn); } /// @@ -189,7 +190,7 @@ private static byte[][] GetSqlServerSPNs(string hostNameOrAddress, string portOr /// Host name in certificate /// Used for the path to the Server Certificate /// SNITCPHandle - private static SNITCPHandle CreateTcpHandle( + private static SniTcpHandle CreateTcpHandle( DataSource details, TimeoutTimer timeout, bool parallel, @@ -207,7 +208,7 @@ private static SNITCPHandle CreateTcpHandle( string hostName = details.ServerName; if (string.IsNullOrWhiteSpace(hostName)) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.TCP_PROV, 0, SNICommon.InvalidConnStringError, Strings.SNI_ERROR_25); + SniLoadHandle.SingletonInstance.LastError = new SniError(SniProviders.TCP_PROV, 0, SniCommon.InvalidConnStringError, Strings.SNI_ERROR_25); return null; } @@ -218,12 +219,12 @@ private static SNITCPHandle CreateTcpHandle( try { details.ResolvedPort = port = isAdminConnection ? - SSRP.GetDacPortByInstanceName(hostName, details.InstanceName, timeout, parallel, ipPreference) : - SSRP.GetPortByInstanceName(hostName, details.InstanceName, timeout, parallel, ipPreference); + SsrpClient.GetDacPortByInstanceName(hostName, details.InstanceName, timeout, parallel, ipPreference) : + SsrpClient.GetPortByInstanceName(hostName, details.InstanceName, timeout, parallel, ipPreference); } catch (SocketException se) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.TCP_PROV, SNICommon.ErrorLocatingServerInstance, se); + SniLoadHandle.SingletonInstance.LastError = new SniError(SniProviders.TCP_PROV, SniCommon.ErrorLocatingServerInstance, se); return null; } } @@ -236,7 +237,7 @@ private static SNITCPHandle CreateTcpHandle( port = isAdminConnection ? DefaultSqlServerDacPort : DefaultSqlServerPort; } - return new SNITCPHandle(hostName, port, timeout, parallel, ipPreference, cachedFQDN, ref pendingDNSInfo, + return new SniTcpHandle(hostName, port, timeout, parallel, ipPreference, cachedFQDN, ref pendingDNSInfo, tlsFirst, hostNameInCertificate, serverCertificateFilename); } @@ -250,24 +251,24 @@ private static SNITCPHandle CreateTcpHandle( /// Host name in certificate /// Used for the path to the Server Certificate /// SNINpHandle - private static SNINpHandle CreateNpHandle(DataSource details, TimeoutTimer timeout, bool parallel, bool tlsFirst, string hostNameInCertificate, string serverCertificateFilename) + private static SniNpHandle CreateNpHandle(DataSource details, TimeoutTimer timeout, bool parallel, bool tlsFirst, string hostNameInCertificate, string serverCertificateFilename) { if (parallel) { // Connecting to a SQL Server instance using the MultiSubnetFailover connection option is only supported when using the TCP protocol - SNICommon.ReportSNIError(SNIProviders.NP_PROV, 0, SNICommon.MultiSubnetFailoverWithNonTcpProtocol, Strings.SNI_ERROR_49); + SniCommon.ReportSNIError(SniProviders.NP_PROV, 0, SniCommon.MultiSubnetFailoverWithNonTcpProtocol, Strings.SNI_ERROR_49); return null; } - return new SNINpHandle(details.PipeHostName, details.PipeName, timeout, tlsFirst, hostNameInCertificate, serverCertificateFilename); + return new SniNpHandle(details.PipeHostName, details.PipeName, timeout, tlsFirst, hostNameInCertificate, serverCertificateFilename); } /// /// Get last SNI error on this thread /// /// - internal SNIError GetLastError() + internal SniError GetLastError() { - return SNILoadHandle.SingletonInstance.LastError; + return SniLoadHandle.SingletonInstance.LastError; } /// @@ -384,11 +385,11 @@ private DataSource(string dataSource) if (_dataSourceAfterTrimmingProtocol.Contains(Slash)) // Pipe paths only allow back slashes { if (ResolvedProtocol == Protocol.None) - ReportSNIError(SNIProviders.INVALID_PROV); + ReportSNIError(SniProviders.INVALID_PROV); else if (ResolvedProtocol == Protocol.NP) - ReportSNIError(SNIProviders.NP_PROV); + ReportSNIError(SniProviders.NP_PROV); else if (ResolvedProtocol == Protocol.TCP) - ReportSNIError(SNIProviders.TCP_PROV); + ReportSNIError(SniProviders.TCP_PROV); } } @@ -441,8 +442,8 @@ internal static string GetLocalDBInstance(string dataSource, out bool error) } else if (index > 0) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.ErrorLocatingServerInstance, Strings.SNI_ERROR_26); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNIProxy), EventType.ERR, "Incompatible use of prefix with LocalDb: '{0}'", dataSource); + SniLoadHandle.SingletonInstance.LastError = new SniError(SniProviders.INVALID_PROV, 0, SniCommon.ErrorLocatingServerInstance, Strings.SNI_ERROR_26); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniProxy), EventType.ERR, "Incompatible use of prefix with LocalDb: '{0}'", dataSource); error = true; } else if (index == 0) @@ -460,7 +461,7 @@ internal static string GetLocalDBInstance(string dataSource, out bool error) } else { - SNILoadHandle.SingletonInstance.LastError = new SNIError(SNIProviders.INVALID_PROV, 0, SNICommon.LocalDBNoInstanceName, Strings.SNI_ERROR_51); + SniLoadHandle.SingletonInstance.LastError = new SniError(SniProviders.INVALID_PROV, 0, SniCommon.LocalDBNoInstanceName, Strings.SNI_ERROR_51); error = true; } } @@ -527,7 +528,7 @@ private bool InferConnectionDetails() // Bad Data Source like "server, " if (string.IsNullOrEmpty(parameter)) { - ReportSNIError(SNIProviders.INVALID_PROV); + ReportSNIError(SniProviders.INVALID_PROV); return false; } @@ -539,21 +540,21 @@ private bool InferConnectionDetails() else if (ResolvedProtocol != Protocol.TCP) { // Parameter has been specified for non-TCP protocol. This is not allowed. - ReportSNIError(SNIProviders.INVALID_PROV); + ReportSNIError(SniProviders.INVALID_PROV); return false; } int port; if (!int.TryParse(parameter, out port)) { - ReportSNIError(SNIProviders.TCP_PROV); + ReportSNIError(SniProviders.TCP_PROV); return false; } // If the user explicitly specified a invalid port in the connection string. if (port < 1) { - ReportSNIError(SNIProviders.TCP_PROV); + ReportSNIError(SniProviders.TCP_PROV); return false; } @@ -567,13 +568,13 @@ private bool InferConnectionDetails() if (string.IsNullOrWhiteSpace(InstanceName)) { - ReportSNIError(SNIProviders.INVALID_PROV); + ReportSNIError(SniProviders.INVALID_PROV); return false; } if (DefaultSqlServerInstanceName.Equals(InstanceName)) { - ReportSNIError(SNIProviders.INVALID_PROV); + ReportSNIError(SniProviders.INVALID_PROV); return false; } @@ -585,9 +586,9 @@ private bool InferConnectionDetails() return true; } - private void ReportSNIError(SNIProviders provider) + private void ReportSNIError(SniProviders provider) { - SNILoadHandle.SingletonInstance.LastError = new SNIError(provider, 0, SNICommon.InvalidConnStringError, Strings.SNI_ERROR_25); + SniLoadHandle.SingletonInstance.LastError = new SniError(provider, 0, SniCommon.InvalidConnStringError, Strings.SNI_ERROR_25); IsBadDataSource = true; } @@ -614,14 +615,14 @@ private bool InferNamedPipesInformation() } else { - ReportSNIError(SNIProviders.NP_PROV); + ReportSNIError(SniProviders.NP_PROV); return false; } } else { PipeHostName = ServerName = _dataSourceAfterTrimmingProtocol; - PipeName = SNINpHandle.DefaultPipePath; + PipeName = SniNpHandle.DefaultPipePath; } InferLocalServerName(); @@ -637,7 +638,7 @@ private bool InferNamedPipesInformation() // Another valid Sql named pipe for an named instance is \\.\pipe\MSSQL$MYINSTANCE\sql\query if (tokensByBackSlash.Length < 6) { - ReportSNIError(SNIProviders.NP_PROV); + ReportSNIError(SniProviders.NP_PROV); return false; } @@ -645,14 +646,14 @@ private bool InferNamedPipesInformation() if (string.IsNullOrEmpty(host)) { - ReportSNIError(SNIProviders.NP_PROV); + ReportSNIError(SniProviders.NP_PROV); return false; } //Check if the "pipe" keyword is the first part of path if (!PipeToken.Equals(tokensByBackSlash[3])) { - ReportSNIError(SNIProviders.NP_PROV); + ReportSNIError(SniProviders.NP_PROV); return false; } @@ -684,7 +685,7 @@ private bool InferNamedPipesInformation() } catch (UriFormatException) { - ReportSNIError(SNIProviders.NP_PROV); + ReportSNIError(SniProviders.NP_PROV); return false; } @@ -696,7 +697,7 @@ private bool InferNamedPipesInformation() else if (ResolvedProtocol != Protocol.NP) { // In case the path began with a "\\" and protocol was not Named Pipes - ReportSNIError(SNIProviders.NP_PROV); + ReportSNIError(SniProviders.NP_PROV); return false; } return true; @@ -704,7 +705,9 @@ private bool InferNamedPipesInformation() return false; } - private static bool IsLocalHost(string serverName) - => ".".Equals(serverName) || "(local)".Equals(serverName) || "localhost".Equals(serverName); + private static bool IsLocalHost(string serverName) => + ".".Equals(serverName) || "(local)".Equals(serverName) || "localhost".Equals(serverName); } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniSmuxFlags.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniSmuxFlags.netcore.cs new file mode 100644 index 0000000000..feffee9113 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniSmuxFlags.netcore.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if NET + +using System; + +namespace Microsoft.Data.SqlClient.ManagedSni +{ + /// + /// SMUX packet flags + /// + [Flags] + internal enum SniSmuxFlags + { + // @TODO: Should probably drop the SMUX prefix - it's pretty obvious since it's in the SmuxFlags enum. + /// + /// Begin SMUX connection. + /// + SMUX_SYN = 1, + + /// + /// Acknowledge SMUX packets. + /// + SMUX_ACK = 2, + + /// + /// End SMUX connection. + /// + SMUX_FIN = 4, + + /// + /// SMUX data packet. + /// + SMUX_DATA = 8 + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniSmuxHeader.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniSmuxHeader.netcore.cs new file mode 100644 index 0000000000..10bde29f7f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniSmuxHeader.netcore.cs @@ -0,0 +1,69 @@ +// 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. + +#if NET + +using System; +using System.Buffers.Binary; + +namespace Microsoft.Data.SqlClient.ManagedSni +{ + /// + /// SMUX packet header + /// + internal sealed class SniSmuxHeader + { + public const int HEADER_LENGTH = 16; + + public byte SMID; + public byte flags; + public ushort sessionId; + public uint length; + public uint sequenceNumber; + public uint highwater; + + public void Read(byte[] bytes) + { + SMID = bytes[0]; + flags = bytes[1]; + Span span = bytes.AsSpan(); + sessionId = BinaryPrimitives.ReadUInt16LittleEndian(span.Slice(2)); + length = BinaryPrimitives.ReadUInt32LittleEndian(span.Slice(4)) - SniSmuxHeader.HEADER_LENGTH; + sequenceNumber = BinaryPrimitives.ReadUInt32LittleEndian(span.Slice(8)); + highwater = BinaryPrimitives.ReadUInt32LittleEndian(span.Slice(12)); + } + + public void Write(Span bytes) + { + uint value = highwater; + // access the highest element first to cause the largest range check in the jit, then fill in the rest of the value and carry on as normal + bytes[15] = (byte)((value >> 24) & 0xff); + bytes[12] = (byte)(value & 0xff); // BitConverter.GetBytes(_currentHeader.highwater).CopyTo(headerBytes, 12); + bytes[13] = (byte)((value >> 8) & 0xff); + bytes[14] = (byte)((value >> 16) & 0xff); + + bytes[0] = SMID; // BitConverter.GetBytes(_currentHeader.SMID).CopyTo(headerBytes, 0); + bytes[1] = flags; // BitConverter.GetBytes(_currentHeader.flags).CopyTo(headerBytes, 1); + + value = sessionId; + bytes[2] = (byte)(value & 0xff); // BitConverter.GetBytes(_currentHeader.sessionId).CopyTo(headerBytes, 2); + bytes[3] = (byte)((value >> 8) & 0xff); + + value = length; + bytes[4] = (byte)(value & 0xff); // BitConverter.GetBytes(_currentHeader.length).CopyTo(headerBytes, 4); + bytes[5] = (byte)((value >> 8) & 0xff); + bytes[6] = (byte)((value >> 16) & 0xff); + bytes[7] = (byte)((value >> 24) & 0xff); + + value = sequenceNumber; + bytes[8] = (byte)(value & 0xff); // BitConverter.GetBytes(_currentHeader.sequenceNumber).CopyTo(headerBytes, 8); + bytes[9] = (byte)((value >> 8) & 0xff); + bytes[10] = (byte)((value >> 16) & 0xff); + bytes[11] = (byte)((value >> 24) & 0xff); + + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniSslStream.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniSslStream.netcore.cs new file mode 100644 index 0000000000..a7fc289d7f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniSslStream.netcore.cs @@ -0,0 +1,81 @@ +// 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. + +#if NET + +using System; +using System.IO; +using System.Net.Security; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Data.SqlClient.ManagedSni +{ + /// + /// This class extends SslStream to customize stream behavior for Managed SNI implementation. + /// + internal sealed class SniSslStream : SslStream + { + private readonly ConcurrentQueueSemaphore _writeAsyncSemaphore; + private readonly ConcurrentQueueSemaphore _readAsyncSemaphore; + + public SniSslStream( + Stream innerStream, + bool leaveInnerStreamOpen, + RemoteCertificateValidationCallback userCertificateValidationCallback) + : base(innerStream, leaveInnerStreamOpen, userCertificateValidationCallback) + { + _writeAsyncSemaphore = new ConcurrentQueueSemaphore(1); + _readAsyncSemaphore = new ConcurrentQueueSemaphore(1); + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); + } + + public override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + { + await _readAsyncSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + return await base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniSslStream), EventType.ERR, "Internal Exception occurred while reading data: {0}", args0: e?.Message); + throw; + } + finally + { + _readAsyncSemaphore.Release(); + } + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return WriteAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); + } + + public override async ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default) + { + await _writeAsyncSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + await base.WriteAsync(buffer, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) + { + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniSslStream), EventType.ERR, "Internal Exception occurred while reading data: {0}", args0: e?.Message); + throw; + } + finally + { + _writeAsyncSemaphore.Release(); + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs similarity index 80% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs index ca68cec63c..8435a3d3d5 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNITcpHandle.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SniTcpHandle.netcore.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; using System.Collections.Generic; using System.ComponentModel; @@ -15,12 +17,12 @@ using Microsoft.Data.Common; using Microsoft.Data.ProviderBase; -namespace Microsoft.Data.SqlClient.SNI +namespace Microsoft.Data.SqlClient.ManagedSni { /// /// TCP connection handle /// - internal sealed class SNITCPHandle : SNIPhysicalHandle + internal sealed class SniTcpHandle : SniPhysicalHandle { private readonly string _targetServer; private readonly object _sendSync; @@ -33,8 +35,8 @@ internal sealed class SNITCPHandle : SNIPhysicalHandle private Stream _stream; private SslStream _sslStream; private SslOverTdsStream _sslOverTdsStream; - private SNIAsyncCallback _receiveCallback; - private SNIAsyncCallback _sendCallback; + private SniAsyncCallback _receiveCallback; + private SniAsyncCallback _sendCallback; private bool _validateCert = true; private int _bufferSize = TdsEnums.DEFAULT_LOGIN_PACKET_SIZE; @@ -70,7 +72,7 @@ public override void Dispose() //Release any references held by _stream. _stream = null; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, All streams disposed.", args0: _connectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, All streams disposed.", args0: _connectionId); } } @@ -124,7 +126,7 @@ public override int ProtocolVersion /// Support TDS8.0 /// Host Name in Certificate /// Used for the path to the Server Certificate - public SNITCPHandle( + public SniTcpHandle( string serverName, int port, TimeoutTimer timeout, @@ -136,9 +138,9 @@ public SNITCPHandle( string hostNameInCertificate, string serverCertificateFilename) { - using (TrySNIEventScope.Create(nameof(SNITCPHandle))) + using (TrySNIEventScope.Create(nameof(SniTcpHandle))) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Setting server name = {1}", args0: _connectionId, args1: serverName); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Setting server name = {1}", args0: _connectionId, args1: serverName); _targetServer = serverName; _tlsFirst = tlsFirst; @@ -153,7 +155,7 @@ public SNITCPHandle( { bool reportError = true; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Connecting to serverName {1} and port {2}", args0: _connectionId, args1: serverName, args2: port); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Connecting to serverName {1} and port {2}", args0: _connectionId, args1: serverName, args2: port); // We will always first try to connect with serverName as before and let DNS resolve the serverName. // If DNS resolution fails, we will try with IPs in the DNS cache if they exist. We try with cached IPs based on IPAddressPreference. // Exceptions will be thrown to the caller and be handled as before. @@ -179,13 +181,13 @@ public SNITCPHandle( { if (hasCachedDNSInfo == false) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0}, Cached DNS Info not found, exception occurred thrown: {1}", args0: _connectionId, args1: ex?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, Cached DNS Info not found, exception occurred thrown: {1}", args0: _connectionId, args1: ex?.Message); throw; } else { int portRetry = string.IsNullOrEmpty(cachedDNSInfo.Port) ? port : int.Parse(cachedDNSInfo.Port); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Retrying with cached DNS IP Address {1} and port {2}", args0: _connectionId, args1: cachedDNSInfo.AddrIPv4, args2: cachedDNSInfo.Port); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Retrying with cached DNS IP Address {1} and port {2}", args0: _connectionId, args1: cachedDNSInfo.AddrIPv4, args2: cachedDNSInfo.Port); string firstCachedIP; string secondCachedIP; @@ -221,7 +223,7 @@ public SNITCPHandle( if (exRetry is SocketException || exRetry is ArgumentNullException || exRetry is ArgumentException || exRetry is ArgumentOutOfRangeException || exRetry is AggregateException) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Retrying exception {1}", args0: _connectionId, args1: exRetry?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Retrying exception {1}", args0: _connectionId, args1: exRetry?.Message); if (parallel) { _socket = TryConnectParallel(secondCachedIP, portRetry, timeout, ref reportError, cachedFQDN, ref pendingDNSInfo); @@ -233,7 +235,7 @@ public SNITCPHandle( } else { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0}, Retry failed, exception occurred: {1}", args0: _connectionId, args1: exRetry?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, Retry failed, exception occurred: {1}", args0: _connectionId, args1: exRetry?.Message); throw; } } @@ -255,15 +257,15 @@ public SNITCPHandle( if (reportError) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0} could not be opened, exception occurred: {1}", args0: _connectionId, args1: Strings.SNI_ERROR_40); - ReportTcpSNIError(0, SNICommon.ConnOpenFailedError, Strings.SNI_ERROR_40); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0} could not be opened, exception occurred: {1}", args0: _connectionId, args1: Strings.SNI_ERROR_40); + ReportTcpSNIError(0, SniCommon.ConnOpenFailedError, Strings.SNI_ERROR_40); } - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0} Socket could not be opened.", args0: _connectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0} Socket could not be opened.", args0: _connectionId); return; } _socket.NoDelay = true; - _tcpStream = new SNINetworkStream(_socket, true); + _tcpStream = new SniNetworkStream(_socket, true); Stream stream = _tcpStream; if (!_tlsFirst) @@ -271,24 +273,24 @@ public SNITCPHandle( _sslOverTdsStream = new SslOverTdsStream(_tcpStream, _connectionId); stream = _sslOverTdsStream; } - _sslStream = new SNISslStream(stream, true, new RemoteCertificateValidationCallback(ValidateServerCertificate)); + _sslStream = new SniSslStream(stream, true, new RemoteCertificateValidationCallback(ValidateServerCertificate)); } catch (SocketException se) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0} Socket exception occurred: Error Code {1}, Message {2}", args0: _connectionId, args1: se?.SocketErrorCode, args2: se?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0} Socket exception occurred: Error Code {1}, Message {2}", args0: _connectionId, args1: se?.SocketErrorCode, args2: se?.Message); ReportTcpSNIError(se); return; } catch (Exception e) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0} Exception occurred: {1}", args0: _connectionId, args1: e?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0} Exception occurred: {1}", args0: _connectionId, args1: e?.Message); ReportTcpSNIError(e); return; } _stream = _tcpStream; _status = TdsEnums.SNI_SUCCESS; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0} Socket opened successfully, TCP stream ready.", args0: _connectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0} Socket opened successfully, TCP stream ready.", args0: _connectionId); } } @@ -297,21 +299,21 @@ public SNITCPHandle( // Only write to the DNS cache when we receive IsSupported flag as true in the Feature Ext Ack from server. private Socket TryConnectParallel(string hostName, int port, TimeoutTimer timeout, ref bool callerReportError, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo) { - using (TrySNIEventScope.Create(nameof(SNITCPHandle))) + using (TrySNIEventScope.Create(nameof(SniTcpHandle))) { Socket availableSocket = null; bool isInfiniteTimeOut = timeout.IsInfinite; IPAddress[] serverAddresses = isInfiniteTimeOut - ? SNICommon.GetDnsIpAddresses(hostName) - : SNICommon.GetDnsIpAddresses(hostName, timeout); + ? SniCommon.GetDnsIpAddresses(hostName) + : SniCommon.GetDnsIpAddresses(hostName, timeout); if (serverAddresses.Length > MaxParallelIpAddresses) { // Fail if above 64 to match legacy behavior callerReportError = false; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0} serverAddresses.Length {1} Exception: {2}", args0: _connectionId, args1: serverAddresses.Length, args2: Strings.SNI_ERROR_47); - ReportTcpSNIError(0, SNICommon.MultiSubnetFailoverWithMoreThan64IPs, Strings.SNI_ERROR_47); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0} serverAddresses.Length {1} Exception: {2}", args0: _connectionId, args1: serverAddresses.Length, args2: Strings.SNI_ERROR_47); + ReportTcpSNIError(0, SniCommon.MultiSubnetFailoverWithMoreThan64IPs, Strings.SNI_ERROR_47); return availableSocket; } @@ -366,13 +368,15 @@ private static IEnumerable GetHostAddressesSortedByPreference(string // Only write to the DNS cache when we receive IsSupported flag as true in the Feature Ext Ack from server. private static Socket Connect(string serverName, int port, TimeoutTimer timeout, SqlConnectionIPAddressPreference ipPreference, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo) { - using (TrySNIEventScope.Create(nameof(SNITCPHandle))) + using (TrySNIEventScope.Create(nameof(SniTcpHandle))) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "IP preference : {0}", Enum.GetName(typeof(SqlConnectionIPAddressPreference), ipPreference)); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "IP preference : {0}", Enum.GetName(typeof(SqlConnectionIPAddressPreference), ipPreference)); bool isInfiniteTimeout = timeout.IsInfinite; IEnumerable ipAddresses = GetHostAddressesSortedByPreference(serverName, ipPreference); + SocketException lastSocketException = null; + foreach (IPAddress ipAddress in ipAddresses) { bool isSocketSelected = false; @@ -388,7 +392,7 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout, // enable keep-alive on socket SetKeepAliveValues(ref socket); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connecting to IP address {0} and port {1} using {2} address family. Is infinite timeout: {3}", ipAddress, port, @@ -424,7 +428,9 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout, { if (timeout.IsExpired) { - return null; + throw new Win32Exception( + TdsEnums.SNI_WAIT_TIMEOUT, + StringsHelper.GetString(Strings.SQL_ConnectTimeout)); } int socketSelectTimeout = @@ -434,16 +440,30 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout, checkWriteLst = new List(1) { socket }; checkErrorLst = new List(1) { socket }; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Determining the status of the socket during the remaining timeout of {0} microseconds.", socketSelectTimeout); Socket.Select(checkReadLst, checkWriteLst, checkErrorLst, socketSelectTimeout); // nothing selected means timeout + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, + "Socket.Select results: checkReadLst.Count: {0}, checkWriteLst.Count: {1}, checkErrorLst.Count: {2}", + checkReadLst.Count, checkWriteLst.Count, checkErrorLst.Count); } while (checkReadLst.Count == 0 && checkWriteLst.Count == 0 && checkErrorLst.Count == 0); // workaround: false positive socket.Connected on linux: https://github.com/dotnet/runtime/issues/55538 isConnected = socket.Connected && checkErrorLst.Count == 0; + if (!isConnected) + { + // Retrieve the socket error code + int socketErrorCode = (int)socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Error); + SocketError socketError = (SocketError)socketErrorCode; + + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, + "Socket connection failed. SocketError: {0} ({1})", socketError, socketErrorCode); + + lastSocketException = new SocketException(socketErrorCode); + } } if (isConnected) @@ -461,6 +481,8 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout, } pendingDNSInfo = new SQLDNSInfo(cachedFQDN, iPv4String, iPv6String, port.ToString()); isSocketSelected = true; + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, + "Connected to socket: {0}", socket.RemoteEndPoint); return socket; } } @@ -468,7 +490,8 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout, { SqlClientEventSource.Log.TryAdvancedTraceEvent( "{0}.{1}{2}THIS EXCEPTION IS BEING SWALLOWED: {3}", - nameof(SNITCPHandle), nameof(Connect), EventType.ERR, e); + nameof(SniTcpHandle), nameof(Connect), EventType.ERR, e); + lastSocketException = e; } finally { @@ -477,13 +500,21 @@ private static Socket Connect(string serverName, int port, TimeoutTimer timeout, } } + if (lastSocketException != null) + { + SqlClientEventSource.Log.TryAdvancedTraceEvent( + "{0}.{1}{2}Last Socket Exception: {3}", + nameof(SniTcpHandle), nameof(Connect), EventType.ERR, lastSocketException); + throw lastSocketException; + } + return null; } } private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, TimeoutTimer timeout, string cachedFQDN, ref SQLDNSInfo pendingDNSInfo) { - using (TrySNIEventScope.Create(nameof(SNITCPHandle))) + using (TrySNIEventScope.Create(nameof(SniTcpHandle))) { if (serverAddresses == null) { @@ -508,7 +539,7 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim // enable keep-alive on socket SetKeepAliveValues(ref socket); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connecting to IP address {0} and port {1} using {2} address family. Is infinite timeout: {3}", address, port, @@ -564,7 +595,7 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim checkErrorLst.Clear(); checkErrorLst.AddRange(socketsInFlight); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Watching pending sockets during the remaining timeout of {0} microseconds.", socketSelectTimeout); @@ -572,20 +603,35 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim Socket.Select(checkReadLst, checkWriteLst, checkErrorLst, socketSelectTimeout); // nothing selected means select timed out } while (checkReadLst.Count == 0 && checkWriteLst.Count == 0 && checkErrorLst.Count == 0 && !timeout.IsExpired); + foreach (Socket socket in checkErrorLst) + { + // Retrieve the socket error code + int socketErrorCode = (int)socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Error); + SocketError socketError = (SocketError)socketErrorCode; + + // Log any failed sockets + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, + "Socket connection failed for {0}. SocketError: {1} ({2})", + sockets[socket], socketError, socketErrorCode); + + lastError = new SocketException(socketErrorCode); + } + } catch (SocketException e) { // Socket.Select can throw if one of the sockets has issues. That socket will be in checkErrorLst. // Log the error and let that socket be removed from socketsInFlight below. SqlClientEventSource.Log.TryAdvancedTraceEvent( - "{0}.{1}{2}THIS EXCEPTION IS BEING SWALLOWED: {3}", nameof(SNITCPHandle), nameof(ParallelConnect), EventType.ERR, e); + "{0}.{1}{2}THIS EXCEPTION IS BEING SWALLOWED: {3}", nameof(SniTcpHandle), nameof(ParallelConnect), EventType.ERR, e); lastError = e; } if (timeout.IsExpired) { SqlClientEventSource.Log.TryAdvancedTraceEvent( - "{0}.{1}{2}ParallelConnect timeout expired.", nameof(SNITCPHandle), nameof(ParallelConnect), EventType.INFO); + "{0}.{1}{2}ParallelConnect timeout expired.", nameof(SniTcpHandle), nameof(ParallelConnect), EventType.INFO); + // We will throw below after cleanup break; } @@ -596,7 +642,7 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim if (!checkErrorLst.Contains(s) && s.Connected) { connectedSocket = s; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connected to endpoint: {0}", connectedSocket.RemoteEndPoint); connectedSocket.Blocking = true; string iPv4String = null; @@ -622,7 +668,7 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim foreach (Socket socket in checkErrorLst) { SqlClientEventSource.Log.TryAdvancedTraceEvent( - "{0}.{1}{2}Failed to connect to endpoint: {3}. Error: {4}", nameof(SNITCPHandle), + "{0}.{1}{2}Failed to connect to endpoint: {3}. Error: {4}", nameof(SniTcpHandle), nameof(ParallelConnect), EventType.INFO, sockets[socket], lastError); socketsInFlight.Remove(socket); } @@ -631,7 +677,7 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim foreach (Socket socket in checkWriteLst) { SqlClientEventSource.Log.TryAdvancedTraceEvent( - "{0}.{1}{2}Failed to connect to endpoint: {3}. Error: {4}", nameof(SNITCPHandle), + "{0}.{1}{2}Failed to connect to endpoint: {3}. Error: {4}", nameof(SniTcpHandle), nameof(ParallelConnect), EventType.INFO, sockets[socket], lastError); socketsInFlight.Remove(socket); } @@ -644,7 +690,7 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim if (socket != connectedSocket) { SqlClientEventSource.Log.TryAdvancedTraceEvent( - "{0}.{1}{2}Disposing non-selected socket for endpoint: {3}", nameof(SNITCPHandle), + "{0}.{1}{2}Disposing non-selected socket for endpoint: {3}", nameof(SniTcpHandle), nameof(ParallelConnect), EventType.INFO, sockets[socket]); socket?.Dispose(); } @@ -652,9 +698,21 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim if (connectedSocket == null) { + if (timeout.IsExpired) + { + throw new Win32Exception( + TdsEnums.SNI_WAIT_TIMEOUT, + StringsHelper.GetString(Strings.SQL_ConnectTimeout)); + } + SqlClientEventSource.Log.TryAdvancedTraceEvent( - "{0}.{1}{2}No socket connections succeeded. Last error: {3}", - nameof(SNITCPHandle), nameof(ParallelConnect), EventType.ERR, lastError); + "{0}.{1}{2} No socket connections succeeded. Last error: {3}", + nameof(SniTcpHandle), nameof(ParallelConnect), EventType.ERR, lastError); + + if (lastError != null) + { + throw lastError; + } } return connectedSocket; @@ -666,7 +724,7 @@ private static Socket ParallelConnect(IPAddress[] serverAddresses, int port, Tim /// public override uint EnableSsl(uint options) { - using (TrySNIEventScope.Create(nameof(SNIHandle))) + using (TrySNIEventScope.Create(nameof(SniHandle))) { _validateCert = (options & TdsEnums.SNI_SSL_VALIDATE_CERTIFICATE) != 0; @@ -687,17 +745,17 @@ public override uint EnableSsl(uint options) } catch (AuthenticationException aue) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0}, Authentication exception occurred: {1}", args0: _connectionId, args1: aue?.Message); - return ReportTcpSNIError(aue, SNIError.CertificateValidationErrorCode); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, Authentication exception occurred: {1}", args0: _connectionId, args1: aue?.Message); + return ReportTcpSNIError(aue, SniError.CertificateValidationErrorCode); } catch (InvalidOperationException ioe) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0}, Invalid Operation Exception occurred: {1}", args0: _connectionId, args1: ioe?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, Invalid Operation Exception occurred: {1}", args0: _connectionId, args1: ioe?.Message); return ReportTcpSNIError(ioe); } _stream = _sslStream; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, SSL enabled successfully.", args0: _connectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, SSL enabled successfully.", args0: _connectionId); return TdsEnums.SNI_SUCCESS; } } @@ -707,14 +765,14 @@ public override uint EnableSsl(uint options) /// public override void DisableSsl() { - using (TrySNIEventScope.Create(nameof(SNITCPHandle))) + using (TrySNIEventScope.Create(nameof(SniTcpHandle))) { _sslStream.Dispose(); _sslStream = null; _sslOverTdsStream?.Dispose(); _sslOverTdsStream = null; _stream = _tcpStream; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, SSL Disabled. Communication will continue on TCP Stream.", args0: _connectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, SSL Disabled. Communication will continue on TCP Stream.", args0: _connectionId); } } @@ -730,12 +788,12 @@ private bool ValidateServerCertificate(object sender, X509Certificate serverCert { if (!_validateCert) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Certificate will not be validated.", args0: _connectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Certificate will not be validated.", args0: _connectionId); return true; } - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Certificate will be validated for Target Server name", args0: _connectionId); - return SNICommon.ValidateSslServerCertificate(_connectionId, _targetServer, _hostNameInCertificate, serverCertificate, _serverCertificateFilename, policyErrors); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Certificate will be validated for Target Server name", args0: _connectionId); + return SniCommon.ValidateSslServerCertificate(_connectionId, _targetServer, _hostNameInCertificate, serverCertificate, _serverCertificateFilename, policyErrors); } /// @@ -752,7 +810,7 @@ public override void SetBufferSize(int bufferSize) /// /// SNI packet /// SNI error code - public override uint Send(SNIPacket packet) + public override uint Send(SniPacket packet) { bool releaseLock = false; try @@ -779,22 +837,22 @@ public override uint Send(SNIPacket packet) try { packet.WriteToStream(_stream); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Data sent to stream synchronously", args0: _connectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Data sent to stream synchronously", args0: _connectionId); return TdsEnums.SNI_SUCCESS; } catch (ObjectDisposedException ode) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0}, ObjectDisposedException occurred: {1}", args0: _connectionId, args1: ode?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, ObjectDisposedException occurred: {1}", args0: _connectionId, args1: ode?.Message); return ReportTcpSNIError(ode); } catch (SocketException se) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0}, SocketException occurred: {1}", args0: _connectionId, args1: se?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, SocketException occurred: {1}", args0: _connectionId, args1: se?.Message); return ReportTcpSNIError(se); } catch (IOException ioe) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0}, IOException occurred: {1}", args0: _connectionId, args1: ioe?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, IOException occurred: {1}", args0: _connectionId, args1: ioe?.Message); return ReportTcpSNIError(ioe); } } @@ -809,17 +867,29 @@ public override uint Send(SNIPacket packet) } /// - /// Receive a packet synchronously + /// Receives a packet synchronously. /// - /// SNI packet - /// Timeout in Milliseconds - /// SNI error code - public override uint Receive(out SNIPacket packet, int timeoutInMilliseconds) + /// The received SNI packet. + /// + /// Timeout in milliseconds: + /// - If greater than 0, sets the socket's receive timeout to the specified value. + /// - If equal to -1, represents an infinite timeout (socket timeout is set to 0). + /// - If less than -1 or equal to 0, results in a timeout error. + /// + /// SNI error code indicating the result of the operation. + public override uint Receive(out SniPacket packet, int timeoutInMilliseconds) { - SNIPacket errorPacket; + SniPacket errorPacket; lock (this) { packet = null; + + if (_socket == null) + { + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, Socket is null.", args0: _connectionId); + return ReportTcpSNIError(0, SniCommon.ConnOpenFailedError, Strings.SNI_ERROR_10); + } + try { if (timeoutInMilliseconds > 0) @@ -828,14 +898,13 @@ public override uint Receive(out SNIPacket packet, int timeoutInMilliseconds) } else if (timeoutInMilliseconds == -1) { - // SqlClient internally represents infinite timeout by -1, and for TcpClient this is translated to a timeout of 0 - _socket.ReceiveTimeout = 0; + _socket.ReceiveTimeout = Timeout.Infinite; } else { // otherwise it is timeout for 0 or less than -1 - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0}, Error 258, Timeout error occurred.", args0: _connectionId); - ReportTcpSNIError(0, SNICommon.ConnTimeoutError, Strings.SNI_ERROR_11); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, Error 258, Timeout error occurred.", args0: _connectionId); + ReportTcpSNIError(0, SniCommon.ConnTimeoutError, Strings.SNI_ERROR_11); return TdsEnums.SNI_WAIT_TIMEOUT; } @@ -847,25 +916,25 @@ public override uint Receive(out SNIPacket packet, int timeoutInMilliseconds) errorPacket = packet; packet = null; var e = new Win32Exception(); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0}, Win32 exception occurred: {1}", args0: _connectionId, args1: e?.Message); - return ReportErrorAndReleasePacket(errorPacket, (uint)e.NativeErrorCode, 0, e.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, Win32 exception occurred: {1}", args0: _connectionId, args1: e?.Message); + return ReportErrorAndReleasePacket(errorPacket, e.NativeErrorCode, 0, e.Message); } - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Data read from stream synchronously", args0: _connectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Data read from stream synchronously", args0: _connectionId); return TdsEnums.SNI_SUCCESS; } catch (ObjectDisposedException ode) { errorPacket = packet; packet = null; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0}, ObjectDisposedException occurred: {1}", args0: _connectionId, args1: ode?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, ObjectDisposedException occurred: {1}", args0: _connectionId, args1: ode?.Message); return ReportErrorAndReleasePacket(errorPacket, ode); } catch (SocketException se) { errorPacket = packet; packet = null; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0}, Socket exception occurred: {1}", args0: _connectionId, args1: se?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, Socket exception occurred: {1}", args0: _connectionId, args1: se?.Message); return ReportErrorAndReleasePacket(errorPacket, se); } catch (IOException ioe) @@ -875,16 +944,39 @@ public override uint Receive(out SNIPacket packet, int timeoutInMilliseconds) uint errorCode = ReportErrorAndReleasePacket(errorPacket, ioe); if (ioe.InnerException is SocketException socketException && socketException.SocketErrorCode == SocketError.TimedOut) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0}, IO exception occurred with Wait Timeout (error 258): {1}", args0: _connectionId, args1: ioe?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, IO exception occurred with Wait Timeout (error 258): {1}", args0: _connectionId, args1: ioe?.Message); errorCode = TdsEnums.SNI_WAIT_TIMEOUT; } - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.ERR, "Connection Id {0}, IO exception occurred: {1}", args0: _connectionId, args1: ioe?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.ERR, "Connection Id {0}, IO exception occurred: {1}", args0: _connectionId, args1: ioe?.Message); return errorCode; } finally { - _socket.ReceiveTimeout = 0; + const int resetTimeout = Timeout.Infinite; + + try + { + // Reset the socket timeout to Timeout.Infinite after + // the receive operation is done to avoid blocking the + // thread in case of a timeout error. + _socket.ReceiveTimeout = resetTimeout; + + } + catch (SocketException ex) + { + // We sometimes see setting the ReceiveTimeout fail + // on macOS. There's isn't much we can do about it + // though, so just log and move on. + SqlClientEventSource.Log.TrySNITraceEvent( + nameof(SniTcpHandle), + EventType.ERR, + "Connection Id {0}, Failed to reset socket " + + "receive timeout to {1}: {2}", + _connectionId, + resetTimeout, + ex.Message); + } } } } @@ -894,7 +986,7 @@ public override uint Receive(out SNIPacket packet, int timeoutInMilliseconds) /// /// Receive callback /// Send callback - public override void SetAsyncCallbacks(SNIAsyncCallback receiveCallback, SNIAsyncCallback sendCallback) + public override void SetAsyncCallbacks(SniAsyncCallback receiveCallback, SniAsyncCallback sendCallback) { _receiveCallback = receiveCallback; _sendCallback = sendCallback; @@ -905,12 +997,12 @@ public override void SetAsyncCallbacks(SNIAsyncCallback receiveCallback, SNIAsyn /// /// SNI packet /// SNI error code - public override uint SendAsync(SNIPacket packet) + public override uint SendAsync(SniPacket packet) { - using (TrySNIEventScope.Create(nameof(SNITCPHandle))) + using (TrySNIEventScope.Create(nameof(SniTcpHandle))) { - packet.WriteToStreamAsync(_stream, _sendCallback, SNIProviders.TCP_PROV); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Data sent to stream asynchronously", args0: _connectionId); + packet.WriteToStreamAsync(_stream, _sendCallback, SniProviders.TCP_PROV); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Data sent to stream asynchronously", args0: _connectionId); return TdsEnums.SNI_SUCCESS_IO_PENDING; } } @@ -920,15 +1012,15 @@ public override uint SendAsync(SNIPacket packet) /// /// SNI packet /// SNI error code - public override uint ReceiveAsync(ref SNIPacket packet) + public override uint ReceiveAsync(ref SniPacket packet) { - SNIPacket errorPacket; + SniPacket errorPacket; packet = RentPacket(headerSize: 0, dataSize: _bufferSize); packet.SetAsyncIOCompletionCallback(_receiveCallback); try { packet.ReadFromStreamAsync(_stream); - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Data received from stream asynchronously", args0: _connectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Data received from stream asynchronously", args0: _connectionId); return TdsEnums.SNI_SUCCESS_IO_PENDING; } catch (Exception e) when (e is ObjectDisposedException || e is SocketException || e is IOException) @@ -959,37 +1051,37 @@ public override uint CheckConnection() // return true we can safely determine that the connection is no longer active. if (!_socket.Connected || (_socket.Poll(100, SelectMode.SelectRead) && _socket.Available == 0)) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Socket not usable.", args0: _connectionId); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Socket not usable.", args0: _connectionId); return TdsEnums.SNI_ERROR; } } catch (SocketException se) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, Socket Exception occurred: {1}", args0: _connectionId, args1: se?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, Socket Exception occurred: {1}", args0: _connectionId, args1: se?.Message); return ReportTcpSNIError(se); } catch (ObjectDisposedException ode) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SNITCPHandle), EventType.INFO, "Connection Id {0}, ObjectDisposedException occurred: {1}", args0: _connectionId, args1: ode?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SniTcpHandle), EventType.INFO, "Connection Id {0}, ObjectDisposedException occurred: {1}", args0: _connectionId, args1: ode?.Message); return ReportTcpSNIError(ode); } return TdsEnums.SNI_SUCCESS; } - private uint ReportTcpSNIError(Exception sniException, uint nativeErrorCode = 0) + private uint ReportTcpSNIError(Exception sniException, int nativeErrorCode = 0) { _status = TdsEnums.SNI_ERROR; - return SNICommon.ReportSNIError(SNIProviders.TCP_PROV, SNICommon.InternalExceptionError, sniException, nativeErrorCode); + return SniCommon.ReportSNIError(SniProviders.TCP_PROV, SniCommon.InternalExceptionError, sniException, nativeErrorCode); } - private uint ReportTcpSNIError(uint nativeError, uint sniError, string errorMessage) + private uint ReportTcpSNIError(int nativeError, uint sniError, string errorMessage) { _status = TdsEnums.SNI_ERROR; - return SNICommon.ReportSNIError(SNIProviders.TCP_PROV, nativeError, sniError, errorMessage); + return SniCommon.ReportSNIError(SniProviders.TCP_PROV, nativeError, sniError, errorMessage); } - private uint ReportErrorAndReleasePacket(SNIPacket packet, Exception sniException) + private uint ReportErrorAndReleasePacket(SniPacket packet, Exception sniException) { if (packet != null) { @@ -998,7 +1090,7 @@ private uint ReportErrorAndReleasePacket(SNIPacket packet, Exception sniExceptio return ReportTcpSNIError(sniException); } - private uint ReportErrorAndReleasePacket(SNIPacket packet, uint nativeError, uint sniError, string errorMessage) + private uint ReportErrorAndReleasePacket(SniPacket packet, int nativeError, uint sniError, string errorMessage) { if (packet != null) { @@ -1007,7 +1099,7 @@ private uint ReportErrorAndReleasePacket(SNIPacket packet, uint nativeError, uin return ReportTcpSNIError(nativeError, sniError, errorMessage); } -#if DEBUG + #if DEBUG /// /// Test handle for killing underlying connection /// @@ -1015,7 +1107,7 @@ public override void KillConnection() { _socket.Shutdown(SocketShutdown.Both); } -#endif + #endif internal static void SetKeepAliveValues(ref Socket socket) { @@ -1025,3 +1117,5 @@ internal static void SetKeepAliveValues(ref Socket socket) } } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SslOverTdsStream.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SslOverTdsStream.NetCoreApp.cs similarity index 96% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SslOverTdsStream.NetCoreApp.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SslOverTdsStream.NetCoreApp.cs index be8d1a0160..ff3358c50a 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SslOverTdsStream.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SslOverTdsStream.NetCoreApp.cs @@ -2,26 +2,28 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; using System.Buffers; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.Data.SqlClient.SNI +namespace Microsoft.Data.SqlClient.ManagedSni { internal sealed partial class SslOverTdsStream { - public override int Read(byte[] buffer, int offset, int count) - => Read(buffer.AsSpan(offset, count)); + public override int Read(byte[] buffer, int offset, int count) => + Read(buffer.AsSpan(offset, count)); - public override void Write(byte[] buffer, int offset, int count) - => Write(buffer.AsSpan(offset, count)); + public override void Write(byte[] buffer, int offset, int count) => + Write(buffer.AsSpan(offset, count)); - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - => ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => + ReadAsync(new Memory(buffer, offset, count), cancellationToken).AsTask(); - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - => WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken).AsTask(); + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => + WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken).AsTask(); public override int Read(Span buffer) { @@ -293,3 +295,5 @@ public override async ValueTask WriteAsync(ReadOnlyMemory buffer, Cancella } } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SslOverTdsStream.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SslOverTdsStream.netcore.cs similarity index 98% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SslOverTdsStream.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SslOverTdsStream.netcore.cs index e0f48a28d3..ae3dae0c06 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SslOverTdsStream.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SslOverTdsStream.netcore.cs @@ -2,11 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; using System.IO; using System.IO.Pipes; -namespace Microsoft.Data.SqlClient.SNI +namespace Microsoft.Data.SqlClient.ManagedSni { /// /// SSL encapsulated over TDS transport. During SSL handshake, SSL packets are @@ -123,3 +125,5 @@ private static void SetupPreLoginPacketHeader(byte[] buffer, int dataLength, int } } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SSRP.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SsrpClient.netcore.cs similarity index 95% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SSRP.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SsrpClient.netcore.cs index 085551c16e..1aebffefbe 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SSRP.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/SsrpClient.netcore.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; using System.Collections.Generic; using System.Diagnostics; @@ -12,9 +14,9 @@ using System.Threading.Tasks; using Microsoft.Data.ProviderBase; -namespace Microsoft.Data.SqlClient.SNI +namespace Microsoft.Data.SqlClient.ManagedSni { - internal sealed class SSRP + internal sealed class SsrpClient { private const char SemicolonSeparator = ';'; private const int SqlServerBrowserPort = 1434; //port SQL Server Browser @@ -38,7 +40,7 @@ internal static int GetPortByInstanceName(string browserHostName, string instanc { Debug.Assert(!string.IsNullOrWhiteSpace(browserHostName), "browserHostName should not be null, empty, or whitespace"); Debug.Assert(!string.IsNullOrWhiteSpace(instanceName), "instanceName should not be null, empty, or whitespace"); - using (TrySNIEventScope.Create(nameof(SSRP))) + using (TrySNIEventScope.Create(nameof(SsrpClient))) { byte[] instanceInfoRequest = CreateInstanceInfoRequest(instanceName); byte[] responsePacket = null; @@ -54,7 +56,7 @@ internal static int GetPortByInstanceName(string browserHostName, string instanc // the same error as if the response was empty. The higher error suits all scenarios. // But log it, just in case there is a different, underlying issue that support needs // to troubleshoot. - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SSRP), EventType.ERR, "SocketException Message = {0}", args0: se?.Message); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SsrpClient), EventType.ERR, "SocketException Message = {0}", args0: se?.Message); throw; } @@ -86,7 +88,7 @@ internal static int GetPortByInstanceName(string browserHostName, string instanc private static byte[] CreateInstanceInfoRequest(string instanceName) { Debug.Assert(!string.IsNullOrWhiteSpace(instanceName), "instanceName should not be null, empty, or whitespace"); - using (TrySNIEventScope.Create(nameof(SSRP))) + using (TrySNIEventScope.Create(nameof(SsrpClient))) { const byte ClntUcastInst = 0x04; instanceName += char.MinValue; @@ -170,7 +172,7 @@ private class SsrpResult /// response packet from UDP server private static byte[] SendUDPRequest(string browserHostname, int port, byte[] requestPacket, TimeoutTimer timeout, bool allIPsInParallel, SqlConnectionIPAddressPreference ipPreference) { - using (TrySNIEventScope.Create(nameof(SSRP))) + using (TrySNIEventScope.Create(nameof(SsrpClient))) { Debug.Assert(!string.IsNullOrWhiteSpace(browserHostname), "browserhostname should not be null, empty, or whitespace"); Debug.Assert(port >= 0 && port <= 65535, "Invalid port"); @@ -188,8 +190,8 @@ private static byte[] SendUDPRequest(string browserHostname, int port, byte[] re } IPAddress[] ipAddresses = timeout.IsInfinite - ? SNICommon.GetDnsIpAddresses(browserHostname) - : SNICommon.GetDnsIpAddresses(browserHostname, timeout); + ? SniCommon.GetDnsIpAddresses(browserHostname) + : SniCommon.GetDnsIpAddresses(browserHostname, timeout); Debug.Assert(ipAddresses.Length > 0, "DNS should throw if zero addresses resolve"); IPAddress[] ipv4Addresses = null; @@ -338,10 +340,10 @@ private static SsrpResult SendUDPRequest(IPEndPoint endPoint, byte[] requestPack Task sendTask = client.SendAsync(requestPacket, requestPacket.Length, endPoint); Task receiveTask = null; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SSRP), EventType.INFO, "Waiting for UDP Client to fetch Port info."); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SsrpClient), EventType.INFO, "Waiting for UDP Client to fetch Port info."); if (sendTask.Wait(sendTimeOutMs) && (receiveTask = client.ReceiveAsync()).Wait(receiveTimeOutMs)) { - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SSRP), EventType.INFO, "Received Port info from UDP Client."); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SsrpClient), EventType.INFO, "Received Port info from UDP Client."); result.ResponsePacket = receiveTask.Result.Buffer; } } @@ -358,7 +360,7 @@ private static SsrpResult SendUDPRequest(IPEndPoint endPoint, byte[] requestPack { result.Error = e; } - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SSRP), EventType.INFO, + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SsrpClient), EventType.INFO, "SendUDPRequest ({0}) resulted in exception: {1}", args0: endPoint.ToString(), args1: e.Message); } @@ -368,14 +370,14 @@ private static SsrpResult SendUDPRequest(IPEndPoint endPoint, byte[] requestPack else { result.Error = ae; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SSRP), EventType.INFO, + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SsrpClient), EventType.INFO, "SendUDPRequest ({0}) resulted in exception: {1}", args0: endPoint.ToString(), args1: ae.Message); } } catch (Exception e) { result.Error = e; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SSRP), EventType.INFO, + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SsrpClient), EventType.INFO, "SendUDPRequest ({0}) resulted in exception: {1}", args0: endPoint.ToString(), args1: e.Message); } @@ -396,13 +398,13 @@ internal static string SendBroadcastUDPRequest() // https://docs.microsoft.com/en-us/openspecs/windows_protocols/mc-sqlr/f2640a2d-3beb-464b-a443-f635842ebc3e#Appendix_A_3 int currentTimeOut = FirstTimeoutForCLNT_BCAST_EX; - using (TrySNIEventScope.Create(nameof(SSRP))) + using (TrySNIEventScope.Create(nameof(SsrpClient))) { using (UdpClient clientListener = new UdpClient()) { Task sendTask = clientListener.SendAsync(CLNT_BCAST_EX_Request, CLNT_BCAST_EX_Request.Length, new IPEndPoint(IPAddress.Broadcast, SqlServerBrowserPort)); Task receiveTask = null; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SSRP), EventType.INFO, "Waiting for UDP Client to fetch list of instances."); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SsrpClient), EventType.INFO, "Waiting for UDP Client to fetch list of instances."); Stopwatch sw = new Stopwatch(); //for waiting until 15 sec elapsed sw.Start(); try @@ -410,7 +412,7 @@ internal static string SendBroadcastUDPRequest() while ((receiveTask = clientListener.ReceiveAsync()).Wait(currentTimeOut) && sw.ElapsedMilliseconds <= ReceiveMAXTimeoutsForCLNT_BCAST_EX && receiveTask != null) { currentTimeOut = ReceiveTimeoutsForCLNT_BCAST_EX; - SqlClientEventSource.Log.TrySNITraceEvent(nameof(SSRP), EventType.INFO, "Received instnace info from UDP Client."); + SqlClientEventSource.Log.TrySNITraceEvent(nameof(SsrpClient), EventType.INFO, "Received instnace info from UDP Client."); if (receiveTask.Result.Buffer.Length < ValidResponseSizeForCLNT_BCAST_EX) //discard invalid response { response.Append(Encoding.ASCII.GetString(receiveTask.Result.Buffer, ServerResponseHeaderSizeForCLNT_BCAST_EX, receiveTask.Result.Buffer.Length - ServerResponseHeaderSizeForCLNT_BCAST_EX)); //RESP_DATA(VARIABLE) - 3 (RESP_SIZE + SVR_RESP) @@ -462,3 +464,5 @@ private static void SplitIPv4AndIPv6(IPAddress[] input, out IPAddress[] ipv4Addr } } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.Windows.cs new file mode 100644 index 0000000000..b73f063a9c --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.Windows.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.Data.SqlClient +{ + // This structure is used for transporting packet handle references between the + // TdsParserStateObject base class and Managed or Native implementations. + // It prevents the native IntPtr type from being boxed and prevents the need to cast from + // object which loses compile time type safety. + // It carries type information so that assertions about the type of handle can be made in the + // implemented abstract methods. + // It is a ref struct so that it can only be used to transport the handles and not store them + + // N.B. If you change this type you must also change the version for the other platform + + internal readonly ref struct PacketHandle + { + /// + /// PacketHandle is transporting a native pointer. The NativePointer field is valid. + /// A PacketHandle has this type when managed code is referencing a pointer to a + /// packet which has been read from the native SNI layer. + /// + public const int NativePointerType = 1; + /// + /// PacketHandle is transporting a native packet. The NativePacket field is valid. + /// A PacketHandle has this type when managed code is directly referencing a packet + /// which is due to be passed to the native SNI layer. + /// + public const int NativePacketType = 2; + + #if NET + /// + /// PacketHandle is transporting a managed packet. The ManagedPacket field is valid. + /// A PacketHandle used by the managed SNI layer will always have this type. + /// + public const int ManagedPacketType = 3; + + public readonly ManagedSni.SniPacket ManagedPacket; + #endif + + public readonly SNIPacket NativePacket; + public readonly IntPtr NativePointer; + public readonly int Type; + + #if NET + private PacketHandle(IntPtr nativePointer, SNIPacket nativePacket, ManagedSni.SniPacket managedPacket, int type) + { + Type = type; + ManagedPacket = managedPacket; + NativePointer = nativePointer; + NativePacket = nativePacket; + } + #else + private PacketHandle(IntPtr nativePointer, SNIPacket nativePacket, int type) + { + Type = type; + NativePointer = nativePointer; + NativePacket = nativePacket; + } + #endif + + #if NET + public static PacketHandle FromManagedPacket(ManagedSni.SniPacket managedPacket) => + new PacketHandle(default, default, managedPacket, ManagedPacketType); + + public static PacketHandle FromNativePointer(IntPtr nativePointer) => + new PacketHandle(nativePointer, default, default, NativePointerType); + + public static PacketHandle FromNativePacket(SNIPacket nativePacket) => + new PacketHandle(default, nativePacket, default, NativePacketType); + #else + public static PacketHandle FromNativePointer(IntPtr nativePointer) => + new PacketHandle(nativePointer, default, NativePointerType); + + public static PacketHandle FromNativePacket(SNIPacket nativePacket) => + new PacketHandle(default, nativePacket, NativePacketType); + #endif + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.Unix.cs new file mode 100644 index 0000000000..42bf62c5f5 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/PacketHandle.netcore.Unix.cs @@ -0,0 +1,53 @@ +// 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. + +#if NET + +namespace Microsoft.Data.SqlClient +{ + // This structure is used for transporting packet handle references between the + // TdsParserStateObject base class and Managed or Native implementations. + // It prevents the native IntPtr type from being boxed and prevents the need to cast from + // object which loses compile time type safety. + // It carries type information so that assertions about the type of handle can be made in the + // implemented abstract methods. + // It is a ref struct so that it can only be used to transport the handles and not store them + + // N.B. If you change this type you must also change the version for the other platform + + internal readonly ref struct PacketHandle + { + /// + /// PacketHandle is transporting a native pointer. The NativePointer field is valid. + /// A PacketHandle has this type when managed code is referencing a pointer to a + /// packet which has been read from the native SNI layer. + /// + public const int NativePointerType = 1; + /// + /// PacketHandle is transporting a native packet. The NativePacket field is valid. + /// A PacketHandle has this type when managed code is directly referencing a packet + /// which is due to be passed to the native SNI layer. + /// + public const int NativePacketType = 2; + /// + /// PacketHandle is transporting a managed packet. The ManagedPacket field is valid. + /// A PacketHandle used by the managed SNI layer will always have this type. + /// + public const int ManagedPacketType = 3; + + public readonly ManagedSni.SniPacket ManagedPacket; + public readonly int Type; + + private PacketHandle(ManagedSni.SniPacket managedPacket, int type) + { + Type = type; + ManagedPacket = managedPacket; + } + + public static PacketHandle FromManagedPacket(ManagedSni.SniPacket managedPacket) => + new PacketHandle(managedPacket, ManagedPacketType); + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/IAppContextSwitchOverridesSection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/IAppContextSwitchOverridesSection.cs new file mode 100644 index 0000000000..6f75b9f335 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/IAppContextSwitchOverridesSection.cs @@ -0,0 +1,11 @@ +// 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. + +namespace Microsoft.Data.SqlClient +{ + internal interface IAppContextSwitchOverridesSection + { + string Value { get; set; } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/ISqlConfigurableRetryCommandSection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/ISqlConfigurableRetryCommandSection.cs new file mode 100644 index 0000000000..3f7c154312 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/ISqlConfigurableRetryCommandSection.cs @@ -0,0 +1,11 @@ +// 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. + +namespace Microsoft.Data.SqlClient +{ + internal interface ISqlConfigurableRetryCommandSection : ISqlConfigurableRetryConnectionSection + { + string AuthorizedSqlCondition { get; set; } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/ISqlConfigurableRetryConnectionSection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/ISqlConfigurableRetryConnectionSection.cs new file mode 100644 index 0000000000..51f92bd3ba --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/ISqlConfigurableRetryConnectionSection.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.Data.SqlClient +{ + internal interface ISqlConfigurableRetryConnectionSection + { + TimeSpan DeltaTime { get; set; } + + TimeSpan MaxTimeInterval { get; set; } + + TimeSpan MinTimeInterval { get; set; } + + int NumberOfTries { get; set; } + + string RetryLogicType { get; set; } + + string RetryMethod { get; set; } + + string TransientErrors { get; set; } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryLogicLoader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryLogicLoader.cs index c558744481..1dc48e8374 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryLogicLoader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryLogicLoader.cs @@ -4,10 +4,15 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.RegularExpressions; +#if NET +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Runtime.Loader; +#endif + namespace Microsoft.Data.SqlClient { /// @@ -15,7 +20,7 @@ namespace Microsoft.Data.SqlClient /// This class shouldn't throw exceptions; /// All exceptions should be handled internally and logged with Event Source. /// - internal sealed partial class SqlConfigurableRetryLogicLoader + internal sealed class SqlConfigurableRetryLogicLoader { private const string TypeName = nameof(SqlConfigurableRetryLogicLoader); @@ -38,18 +43,20 @@ private void AssignProviders(SqlRetryLogicBaseProvider cnnProvider = null, SqlRe /// internal SqlRetryLogicBaseProvider CommandProvider { get; private set; } - public SqlConfigurableRetryLogicLoader(ISqlConfigurableRetryConnectionSection connectionRetryConfigs, - ISqlConfigurableRetryCommandSection commandRetryConfigs, - string cnnSectionName = SqlConfigurableRetryConnectionSection.Name, - string cmdSectionName = SqlConfigurableRetryCommandSection.Name) + public SqlConfigurableRetryLogicLoader( + ISqlConfigurableRetryConnectionSection connectionRetryConfigs, + ISqlConfigurableRetryCommandSection commandRetryConfigs, + string cnnSectionName = SqlConfigurableRetryConnectionSection.Name, + string cmdSectionName = SqlConfigurableRetryCommandSection.Name) { -#if NET + #if NET // Just only one subscription to this event is required. // This class isn't supposed to be called more than one time; // SqlConfigurableRetryLogicManager manages a single instance of this class. System.Runtime.Loader.AssemblyLoadContext.Default.Resolving -= Default_Resolving; System.Runtime.Loader.AssemblyLoadContext.Default.Resolving += Default_Resolving; -#endif + #endif + AssignProviders(connectionRetryConfigs == null ? null : CreateRetryLogicProvider(cnnSectionName, connectionRetryConfigs), commandRetryConfigs == null ? null : CreateRetryLogicProvider(cmdSectionName, commandRetryConfigs)); } @@ -156,10 +163,12 @@ private static SqlRetryLogicBaseProvider ResolveRetryLogicProvider(string config } private static object CreateInstance( -#if NET + #if NET [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicMethods)] -#endif - Type type, string retryMethodName, SqlRetryLogicOption option) + #endif + Type type, + string retryMethodName, + SqlRetryLogicOption option) { string methodName = nameof(CreateInstance); SqlClientEventSource.Log.TryTraceEvent(" Entry point.", TypeName, methodName); @@ -324,5 +333,118 @@ private static ICollection SplitErrorNumberList(string list) } return new HashSet(); } + + #region Type Resolution + + #if NET + private static Assembly AssemblyResolver(AssemblyName arg) + { + string methodName = nameof(AssemblyResolver); + + string fullPath = MakeFullPath(Environment.CurrentDirectory, arg.Name); + SqlClientEventSource.Log.TryTraceEvent(" Looking for '{2}' assembly by '{3}' full path." + , TypeName, methodName, arg, fullPath); + + return fullPath == null ? null : AssemblyLoadContext.Default.LoadFromAssemblyPath(fullPath); + } + + /// + /// Load assemblies on request. + /// + private static Assembly Default_Resolving(AssemblyLoadContext arg1, AssemblyName arg2) + { + string methodName = nameof(Default_Resolving); + + string target = MakeFullPath(Environment.CurrentDirectory, arg2.Name); + SqlClientEventSource.Log.TryTraceEvent(" Looking for '{2}' assembly that is requested by '{3}' ALC from '{4}' path." + , TypeName, methodName, arg2, arg1, target); + + return target == null ? null : arg1.LoadFromAssemblyPath(target); + } + + /// + /// Performs a case-sensitive search to resolve the specified type name + /// and its related assemblies in default assembly load context if they aren't loaded yet. + /// + /// Resolved type if it could resolve the type; otherwise, the `SqlConfigurableRetryFactory` type. + private static Type LoadType(string fullyQualifiedName) + { + string methodName = nameof(LoadType); + SqlClientEventSource.Log.TryTraceEvent(" Entry point.", TypeName, methodName); + + var result = Type.GetType(fullyQualifiedName, AssemblyResolver, TypeResolver); + if (result != null) + { + SqlClientEventSource.Log.TryTraceEvent(" The '{2}' type is resolved.", + TypeName, methodName, result.FullName); + } + else + { + result = typeof(SqlConfigurableRetryFactory); + SqlClientEventSource.Log.TryTraceEvent(" Couldn't resolve the requested type by '{2}'; The internal `{3}` type is returned.", + TypeName, methodName, fullyQualifiedName, result.FullName); + } + SqlClientEventSource.Log.TryTraceEvent(" Exit point.", TypeName, methodName); + return result; + } + + /// + /// If the caller does not have sufficient permissions to read the specified file, + /// no exception is thrown and the method returns null regardless of the existence of path. + /// + private static string MakeFullPath(string directory, string assemblyName, string extension = ".dll") + { + string methodName = nameof(MakeFullPath); + SqlClientEventSource.Log.TryTraceEvent(" Looking for '{2}' assembly in '{3}' directory." + , TypeName, methodName, assemblyName, directory); + string fullPath = Path.Combine(directory, assemblyName); + fullPath = string.IsNullOrEmpty(Path.GetExtension(fullPath)) ? fullPath + extension : fullPath; + SqlClientEventSource.Log.TryTraceEvent(" Looking for '{2}' assembly by '{3}' full path." + , TypeName, methodName, assemblyName, fullPath); + return File.Exists(fullPath) ? fullPath : null; + } + + private static Type TypeResolver(Assembly arg1, string arg2, bool arg3) + { + IEnumerable types = arg1?.ExportedTypes; + Type result = null; + if (types != null) + { + foreach (Type type in types) + { + if (type.FullName == arg2) + { + if (result != null) + { + throw new InvalidOperationException("Sequence contains more than one matching element"); + } + result = type; + } + } + } + if (result == null) + { + throw new InvalidOperationException("Sequence contains no matching element"); + } + return result; + } + #else + /// + /// Performs a case-sensitive search to resolve the specified type name. + /// + /// + /// Resolved type if it could resolve the type; otherwise, the `SqlConfigurableRetryFactory` type. + private static Type LoadType(string fullyQualifiedName) + { + string methodName = nameof(LoadType); + + var result = Type.GetType(fullyQualifiedName); + SqlClientEventSource.Log.TryTraceEvent(" The '{2}' type is resolved." + , TypeName, methodName, result?.FullName); + return result != null ? result : typeof(SqlConfigurableRetryFactory); + } + #endif + + #endregion } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryLogicManager.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryLogicManager.cs index 7871e6522f..aa20f7b845 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryLogicManager.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Reliability/SqlConfigurableRetryLogicManager.cs @@ -86,25 +86,4 @@ internal static SqlRetryLogicBaseProvider CommandProvider } } } - - internal interface IAppContextSwitchOverridesSection - { - string Value { get; set; } - } - - internal interface ISqlConfigurableRetryConnectionSection - { - TimeSpan DeltaTime { get; set; } - TimeSpan MaxTimeInterval { get; set; } - TimeSpan MinTimeInterval { get; set; } - int NumberOfTries { get; set; } - string RetryLogicType { get; set; } - string RetryMethod { get; set; } - string TransientErrors { get; set; } - } - - internal interface ISqlConfigurableRetryCommandSection : ISqlConfigurableRetryConnectionSection - { - string AuthorizedSqlCondition { get; set; } - } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSSPIContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSspiContextProvider.cs similarity index 63% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSSPIContextProvider.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSspiContextProvider.cs index 067682a617..5935b149c8 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSSPIContextProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NativeSspiContextProvider.cs @@ -1,11 +1,12 @@ using System; +using System.Buffers; using System.Diagnostics; #nullable enable namespace Microsoft.Data.SqlClient { - internal sealed class NativeSSPIContextProvider : SSPIContextProvider + internal sealed class NativeSspiContextProvider : SspiContextProvider { private static readonly object s_tdsParserLock = new(); @@ -15,8 +16,6 @@ internal sealed class NativeSSPIContextProvider : SSPIContextProvider // variable to hold max SSPI data size, keep for token from server private volatile static uint s_maxSSPILength; - internal override uint MaxSSPILength => s_maxSSPILength; - private protected override void Initialize() { LoadSSPILibrary(); @@ -35,7 +34,7 @@ private void LoadSSPILibrary() // use local for ref param to defer setting s_maxSSPILength until we know the call succeeded. uint maxLength = 0; - if (0 != SNINativeMethodWrapper.SNISecInitPackage(ref maxLength)) + if (0 != SniNativeWrapper.SniSecInitPackage(ref maxLength)) SSPIError(SQLMessage.SSPIInitializeError(), TdsEnums.INIT_SSPI_PACKAGE); s_maxSSPILength = maxLength; @@ -50,7 +49,7 @@ private void LoadSSPILibrary() } } - internal override void GenerateSspiClientContext(ReadOnlyMemory receivedBuff, ref byte[] sendBuff, ref uint sendLength, byte[][] _sniSpnBuffer) + protected override bool GenerateSspiClientContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, SspiAuthenticationParameters authParams) { #if NETFRAMEWORK SNIHandle handle = _physicalStateObj.Handle; @@ -58,10 +57,24 @@ internal override void GenerateSspiClientContext(ReadOnlyMemory receivedBu Debug.Assert(_physicalStateObj.SessionHandle.Type == SessionHandle.NativeHandleType); SNIHandle handle = _physicalStateObj.SessionHandle.NativeHandle; #endif - if (0 != SNINativeMethodWrapper.SNISecGenClientContext(handle, receivedBuff.Span, sendBuff, ref sendLength, _sniSpnBuffer[0])) + + // This must start as the length of the input, but will be updated by the call to SNISecGenClientContext to the written length + var sendLength = s_maxSSPILength; + var outBuff = outgoingBlobWriter.GetSpan((int)sendLength); + + if (0 != SniNativeWrapper.SniSecGenClientContext(handle, incomingBlob, outBuff, ref sendLength, authParams.Resource)) + { + return false; + } + + if (sendLength > int.MaxValue) { - throw new InvalidOperationException(SQLMessage.SSPIGenerateError()); + throw SQL.InvalidSSPIPacketSize(); // SqlBu 332503 } + + outgoingBlobWriter.Advance((int)sendLength); + + return true; } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSSPIContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSSPIContextProvider.cs deleted file mode 100644 index 8195bae742..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSSPIContextProvider.cs +++ /dev/null @@ -1,41 +0,0 @@ -#if NET - -using System; -using System.Text; -using System.Net.Security; - -#nullable enable - -namespace Microsoft.Data.SqlClient -{ - internal sealed class NegotiateSSPIContextProvider : SSPIContextProvider - { - private NegotiateAuthentication? _negotiateAuth = null; - - internal override void GenerateSspiClientContext(ReadOnlyMemory received, ref byte[] sendBuff, ref uint sendLength, byte[][] _sniSpnBuffer) - { - NegotiateAuthenticationStatusCode statusCode = NegotiateAuthenticationStatusCode.UnknownCredentials; - - for (int i = 0; i < _sniSpnBuffer.Length; i++) - { - _negotiateAuth ??= new(new NegotiateAuthenticationClientOptions { Package = "Negotiate", TargetName = Encoding.Unicode.GetString(_sniSpnBuffer[i]) }); - sendBuff = _negotiateAuth.GetOutgoingBlob(received.Span, out statusCode)!; - // Log session id, status code and the actual SPN used in the negotiation - SqlClientEventSource.Log.TryTraceEvent("{0}.{1} | Info | Session Id {2}, StatusCode={3}, SPN={4}", nameof(NegotiateSSPIContextProvider), - nameof(GenerateSspiClientContext), _physicalStateObj.SessionId, statusCode, _negotiateAuth.TargetName); - if (statusCode == NegotiateAuthenticationStatusCode.Completed || statusCode == NegotiateAuthenticationStatusCode.ContinueNeeded) - break; // Successful case, exit the loop with current SPN. - else - _negotiateAuth = null; // Reset _negotiateAuth to be generated again for next SPN. - } - - if (statusCode is not NegotiateAuthenticationStatusCode.Completed and not NegotiateAuthenticationStatusCode.ContinueNeeded) - { - throw new InvalidOperationException(SQLMessage.SSPIGenerateError() + Environment.NewLine + statusCode); - } - - sendLength = (uint)(sendBuff != null ? sendBuff.Length : 0); - } - } -} -#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSspiContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSspiContextProvider.cs new file mode 100644 index 0000000000..a74651cf2d --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/NegotiateSspiContextProvider.cs @@ -0,0 +1,56 @@ +#if NET + +using System; +using System.Buffers; +using System.Diagnostics; +using System.Net.Security; + +#nullable enable + +namespace Microsoft.Data.SqlClient +{ + internal sealed class NegotiateSspiContextProvider : SspiContextProvider, IDisposable + { + private NegotiateAuthentication? _negotiateAuth; + + protected override bool GenerateSspiClientContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, SspiAuthenticationParameters authParams) + { + var negotiateAuth = GetNegotiateAuthenticationForParams(authParams); + var sendBuff = negotiateAuth.GetOutgoingBlob(incomingBlob, out var statusCode)!; + + // Log session id, status code and the actual SPN used in the negotiation + SqlClientEventSource.Log.TryTraceEvent("{0}.{1} | Info | Session Id {2}, StatusCode={3}, SPN={4}", nameof(NegotiateSspiContextProvider), + nameof(GenerateSspiClientContext), _physicalStateObj.SessionId, statusCode, negotiateAuth.TargetName); + + if (statusCode == NegotiateAuthenticationStatusCode.Completed || statusCode == NegotiateAuthenticationStatusCode.ContinueNeeded) + { + outgoingBlobWriter.Write(sendBuff); + return true; + } + + return false; + } + + public void Dispose() + { + _negotiateAuth?.Dispose(); + } + + private NegotiateAuthentication GetNegotiateAuthenticationForParams(SspiAuthenticationParameters authParams) + { + if (_negotiateAuth is { }) + { + if (string.Equals(_negotiateAuth.TargetName, authParams.Resource, StringComparison.Ordinal)) + { + return _negotiateAuth; + } + + // Dispose of it since we're not going to use it now + _negotiateAuth.Dispose(); + } + + return _negotiateAuth = new(new NegotiateAuthenticationClientOptions { Package = "Negotiate", TargetName = authParams.Resource }); + } + } +} +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SSPIContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SSPIContextProvider.cs deleted file mode 100644 index e9770ebb67..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SSPIContextProvider.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Diagnostics; -using Microsoft.Data.Common; - -#nullable enable - -namespace Microsoft.Data.SqlClient -{ - internal abstract class SSPIContextProvider - { - private TdsParser _parser = null!; - private ServerInfo _serverInfo = null!; - private protected TdsParserStateObject _physicalStateObj = null!; - - internal virtual uint MaxSSPILength => 4096; // TODO: what is a good default here? - - internal void Initialize(ServerInfo serverInfo, TdsParserStateObject physicalStateObj, TdsParser parser) - { - _parser = parser; - _physicalStateObj = physicalStateObj; - _serverInfo = serverInfo; - - Initialize(); - } - - private protected virtual void Initialize() - { - } - - internal abstract void GenerateSspiClientContext(ReadOnlyMemory input, ref byte[] sendBuff, ref uint sendLength, byte[][] _sniSpnBuffer); - - internal void SSPIData(ReadOnlyMemory receivedBuff, ref byte[] sendBuff, ref UInt32 sendLength, byte[] sniSpnBuffer) - => SSPIData(receivedBuff, ref sendBuff, ref sendLength, new[] { sniSpnBuffer }); - - internal void SSPIData(ReadOnlyMemory receivedBuff, ref byte[] sendBuff, ref UInt32 sendLength, byte[][] sniSpnBuffer) - { - using (TrySNIEventScope.Create(nameof(SSPIContextProvider))) - { - try - { - GenerateSspiClientContext(receivedBuff, ref sendBuff, ref sendLength, sniSpnBuffer); - } - catch (Exception e) - { - SSPIError(e.Message + Environment.NewLine + e.StackTrace, TdsEnums.GEN_CLIENT_CONTEXT); - } - } - } - - protected void SSPIError(string error, string procedure) - { - Debug.Assert(!ADP.IsEmpty(procedure), "TdsParser.SSPIError called with an empty or null procedure string"); - Debug.Assert(!ADP.IsEmpty(error), "TdsParser.SSPIError called with an empty or null error string"); - - _physicalStateObj.AddError(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, _serverInfo.ResolvedServerName, error, procedure, 0)); - _parser.ThrowExceptionAndWarning(_physicalStateObj); - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiAuthenticationParameters.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiAuthenticationParameters.cs new file mode 100644 index 0000000000..dce0858360 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiAuthenticationParameters.cs @@ -0,0 +1,23 @@ +#nullable enable + +namespace Microsoft.Data.SqlClient +{ + internal sealed class SspiAuthenticationParameters + { + public SspiAuthenticationParameters(string serverName, string resource) + { + ServerName = serverName; + Resource = resource; + } + + public string Resource { get; } + + public string ServerName { get; } + + public string? UserId { get; set; } + + public string? DatabaseName { get; set; } + + public string? Password { get; set; } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs new file mode 100644 index 0000000000..f45ccee4fd --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SSPI/SspiContextProvider.cs @@ -0,0 +1,117 @@ +using System; +using System.Buffers; +using System.Diagnostics; + +#nullable enable + +namespace Microsoft.Data.SqlClient +{ + internal abstract class SspiContextProvider + { + private TdsParser _parser = null!; + private ServerInfo _serverInfo = null!; + + private SspiAuthenticationParameters? _primaryAuthParams; + private SspiAuthenticationParameters? _secondaryAuthParams; + + private protected TdsParserStateObject _physicalStateObj = null!; + +#if NET + /// + /// for details as to what and means and why there are two. + /// +#endif + internal void Initialize( + ServerInfo serverInfo, + TdsParserStateObject physicalStateObj, + TdsParser parser, + string primaryServerSpn, + string? secondaryServerSpn = null + ) + { + _parser = parser; + _physicalStateObj = physicalStateObj; + _serverInfo = serverInfo; + + var options = parser.Connection.ConnectionOptions; + + SqlClientEventSource.Log.StateDumpEvent(" Initializing provider {0} with SPN={1} and alternate={2}", GetType().FullName, primaryServerSpn, secondaryServerSpn); + + _primaryAuthParams = CreateAuthParams(options, primaryServerSpn); + + if (secondaryServerSpn is { }) + { + _secondaryAuthParams = CreateAuthParams(options, secondaryServerSpn); + } + + Initialize(); + + static SspiAuthenticationParameters CreateAuthParams(SqlConnectionString connString, string serverSpn) => new(connString.DataSource, serverSpn) + { + DatabaseName = connString.InitialCatalog, + UserId = connString.UserID, + Password = connString.Password, + }; + } + + private protected virtual void Initialize() + { + } + + protected abstract bool GenerateSspiClientContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, SspiAuthenticationParameters authParams); + + internal void WriteSSPIContext(ReadOnlySpan receivedBuff, IBufferWriter outgoingBlobWriter) + { + using var _ = TrySNIEventScope.Create(nameof(SspiContextProvider)); + + if (_primaryAuthParams is { }) + { + if (RunGenerateSspiClientContext(receivedBuff, outgoingBlobWriter, _primaryAuthParams)) + { + return; + } + + // remove _primaryAuth from future attempts as it failed + _primaryAuthParams = null; + } + + if (_secondaryAuthParams is { }) + { + if (RunGenerateSspiClientContext(receivedBuff, outgoingBlobWriter, _secondaryAuthParams)) + { + return; + } + + // remove _secondaryAuthParams from future attempts as it failed + _secondaryAuthParams = null; + } + + // If we've hit here, the SSPI context provider implementation failed to generate the SSPI context. + SSPIError(SQLMessage.SSPIGenerateError(), TdsEnums.GEN_CLIENT_CONTEXT); + } + + private bool RunGenerateSspiClientContext(ReadOnlySpan incomingBlob, IBufferWriter outgoingBlobWriter, SspiAuthenticationParameters authParams) + { + try + { + SqlClientEventSource.Log.TryTraceEvent("{0}.{1} | Info | SPN={1}", GetType().FullName, nameof(GenerateSspiClientContext), authParams.Resource); + + return GenerateSspiClientContext(incomingBlob, outgoingBlobWriter, authParams); + } + catch (Exception e) + { + SSPIError(e.Message + Environment.NewLine + e.StackTrace, TdsEnums.GEN_CLIENT_CONTEXT); + return false; + } + } + + protected void SSPIError(string error, string procedure) + { + Debug.Assert(!string.IsNullOrEmpty(procedure), "TdsParser.SSPIError called with an empty or null procedure string"); + Debug.Assert(!string.IsNullOrEmpty(error), "TdsParser.SSPIError called with an empty or null error string"); + + _physicalStateObj.AddError(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, _serverInfo.ResolvedServerName, error, procedure, 0)); + _parser.ThrowExceptionAndWarning(_physicalStateObj); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ITypedGetters.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ITypedGetters.cs deleted file mode 100644 index 406784f2d9..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ITypedGetters.cs +++ /dev/null @@ -1,99 +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.Data; -using System.Data.SqlTypes; - -namespace Microsoft.Data.SqlClient.Server -{ - // Interface for strongly-typed value getters - internal interface ITypedGetters - { - // Null test - bool IsDBNull(int ordinal); - - // Check what type current sql_variant value is - SqlDbType GetVariantType(int ordinal); - - // By value accessors (data copy across the interface boundary implied) - bool GetBoolean(int ordinal); - - byte GetByte(int ordinal); - - long GetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length); - - char GetChar(int ordinal); - - long GetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length); - - short GetInt16(int ordinal); - - int GetInt32(int ordinal); - - long GetInt64(int ordinal); - - float GetFloat(int ordinal); - - double GetDouble(int ordinal); - - string GetString(int ordinal); - - decimal GetDecimal(int ordinal); - - DateTime GetDateTime(int ordinal); - - Guid GetGuid(int ordinal); - - SqlBoolean GetSqlBoolean(int ordinal); - - SqlByte GetSqlByte(int ordinal); - - SqlInt16 GetSqlInt16(int ordinal); - - SqlInt32 GetSqlInt32(int ordinal); - - SqlInt64 GetSqlInt64(int ordinal); - - SqlSingle GetSqlSingle(int ordinal); - - SqlDouble GetSqlDouble(int ordinal); - - SqlMoney GetSqlMoney(int ordinal); - - SqlDateTime GetSqlDateTime(int ordinal); - - SqlDecimal GetSqlDecimal(int ordinal); - - SqlString GetSqlString(int ordinal); - - SqlBinary GetSqlBinary(int ordinal); - - SqlGuid GetSqlGuid(int ordinal); - - SqlChars GetSqlChars(int ordinal); - - SqlBytes GetSqlBytes(int ordinal); - - SqlXml GetSqlXml(int ordinal); - - - // "By reference" accessors - // May hook to buffer. - // Semantics guarantee is that as long as the object exposing the accessor is not logically - // moved to a new set of values and the overall state (open/closed) isn't changed, it will not - // change the underlying value returned and continue to allow access to it. - // - // Example: GetSqlCharsRef called on an event stream value. The back-end optimizes by re-using - // the buffer wrapped by the returned SqlChars on the next row event, but guarantees that it - // won't change the value until the next event is consumed. - // - // Simplest way to guarantee this behavior is to simply call the corresponding by-value accessor. - SqlBytes GetSqlBytesRef(int ordinal); - - SqlChars GetSqlCharsRef(int ordinal); - - SqlXml GetSqlXmlRef(int ordinal); - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ITypedGettersV3.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ITypedGettersV3.cs index 1203e0a6ac..056d89d722 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ITypedGettersV3.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ITypedGettersV3.cs @@ -12,11 +12,11 @@ internal interface ITypedGettersV3 { // Null test // valid for all types - bool IsDBNull(SmiEventSink sink, int ordinal); + bool IsDBNull(int ordinal); // Check what type current sql_variant value is // valid for SqlDbType.Variant - SmiMetaData GetVariantType(SmiEventSink sink, int ordinal); + SmiMetaData GetVariantType(int ordinal); // // Actual value accessors @@ -25,44 +25,44 @@ internal interface ITypedGettersV3 // // valid for SqlDbType.Bit - bool GetBoolean(SmiEventSink sink, int ordinal); + bool GetBoolean(int ordinal); // valid for SqlDbType.TinyInt - byte GetByte(SmiEventSink sink, int ordinal); + byte GetByte(int ordinal); // valid for SqlDbTypes: Binary, VarBinary, Image, Udt, Xml, Char, VarChar, Text, NChar, NVarChar, NText // (Character type support needed for ExecuteXmlReader handling) - long GetBytesLength(SmiEventSink sink, int ordinal); - int GetBytes(SmiEventSink sink, int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length); + long GetBytesLength(int ordinal); + int GetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length); // valid for character types: Char, VarChar, Text, NChar, NVarChar, NText - long GetCharsLength(SmiEventSink sink, int ordinal); - int GetChars(SmiEventSink sink, int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length); - string GetString(SmiEventSink sink, int ordinal); + long GetCharsLength(int ordinal); + int GetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length); + string GetString(int ordinal); // valid for SqlDbType.SmallInt - short GetInt16(SmiEventSink sink, int ordinal); + short GetInt16(int ordinal); // valid for SqlDbType.Int - int GetInt32(SmiEventSink sink, int ordinal); + int GetInt32(int ordinal); // valid for SqlDbType.BigInt, SqlDbType.Money, SqlDbType.SmallMoney - long GetInt64(SmiEventSink sink, int ordinal); + long GetInt64(int ordinal); // valid for SqlDbType.Real - float GetSingle(SmiEventSink sink, int ordinal); + float GetSingle(int ordinal); // valid for SqlDbType.Float - double GetDouble(SmiEventSink sink, int ordinal); + double GetDouble(int ordinal); // valid for SqlDbType.Numeric (uses SqlDecimal since Decimal cannot hold full range) - SqlDecimal GetSqlDecimal(SmiEventSink sink, int ordinal); + SqlDecimal GetSqlDecimal(int ordinal); // valid for DateTime & SmallDateTime - DateTime GetDateTime(SmiEventSink sink, int ordinal); + DateTime GetDateTime(int ordinal); // valid for UniqueIdentifier - Guid GetGuid(SmiEventSink sink, int ordinal); + Guid GetGuid(int ordinal); } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ITypedSetters.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ITypedSetters.cs deleted file mode 100644 index b294c6b347..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ITypedSetters.cs +++ /dev/null @@ -1,96 +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.Data.SqlTypes; - -namespace Microsoft.Data.SqlClient.Server -{ - // interface for strongly-typed value setters - internal interface ITypedSetters - { - // By value setters (data copy across the interface boundary implied) - void SetDBNull(int ordinal); - - void SetBoolean(int ordinal, bool value); - - void SetByte(int ordinal, byte value); - - void SetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length); - - void SetChar(int ordinal, char value); - - void SetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length); - - void SetInt16(int ordinal, short value); - - void SetInt32(int ordinal, int value); - - void SetInt64(int ordinal, long value); - - void SetFloat(int ordinal, float value); - - void SetDouble(int ordinal, double value); - - [ObsoleteAttribute("Not supported as of SMI v2. Will be removed when v1 support dropped. Use setter with offset.")] - void SetString(int ordinal, string value); - - // Method introduced as of SMI V2 - void SetString(int ordinal, string value, int offset); - - void SetDecimal(int ordinal, decimal value); - - void SetDateTime(int ordinal, DateTime value); - - void SetGuid(int ordinal, Guid value); - - void SetSqlBoolean(int ordinal, SqlBoolean value); - - void SetSqlByte(int ordinal, SqlByte value); - - void SetSqlInt16(int ordinal, SqlInt16 value); - - void SetSqlInt32(int ordinal, SqlInt32 value); - - void SetSqlInt64(int ordinal, SqlInt64 value); - - void SetSqlSingle(int ordinal, SqlSingle value); - - void SetSqlDouble(int ordinal, SqlDouble value); - - void SetSqlMoney(int ordinal, SqlMoney value); - - void SetSqlDateTime(int ordinal, SqlDateTime value); - - void SetSqlDecimal(int ordinal, SqlDecimal value); - - [ObsoleteAttribute("Not supported as of SMI v2. Will be removed when v1 support dropped. Use setter with offset.")] - void SetSqlString(int ordinal, SqlString value); - - // Method introduced as of SMI V2 - void SetSqlString(int ordinal, SqlString value, int offset); - - [ObsoleteAttribute("Not supported as of SMI v2. Will be removed when v1 support dropped. Use setter with offset.")] - void SetSqlBinary(int ordinal, SqlBinary value); - - // Method introduced as of SMI V2 - void SetSqlBinary(int ordinal, SqlBinary value, int offset); - - void SetSqlGuid(int ordinal, SqlGuid value); - - [ObsoleteAttribute("Not supported as of SMI v2. Will be removed when v1 support dropped. Use setter with offset.")] - void SetSqlChars(int ordinal, SqlChars value); - - // Method introduced as of SMI V2 - void SetSqlChars(int ordinal, SqlChars value, int offset); - - [ObsoleteAttribute("Not supported as of SMI v2. Will be removed when v1 support dropped. Use setter with offset.")] - void SetSqlBytes(int ordinal, SqlBytes value); - - // Method introduced as of SMI V2 - void SetSqlBytes(int ordinal, SqlBytes value, int offset); - - void SetSqlXml(int ordinal, SqlXml value); - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ITypedSettersV3.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ITypedSettersV3.cs index 6108e787b7..4ae70725ae 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ITypedSettersV3.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ITypedSettersV3.cs @@ -21,58 +21,58 @@ internal interface ITypedSettersV3 // the following pairs are the only setters/sqldbtypes that need this call: // NVarChar/VarChar + SetString (needed only for non-global collation, i.e. SqlString) // Money/SmallMoney + SetInt64 - void SetVariantMetaData(SmiEventSink sink, int ordinal, SmiMetaData metaData); + void SetVariantMetaData(int ordinal, SmiMetaData metaData); // Set value to null // valid for all types - void SetDBNull(SmiEventSink sink, int ordinal); + void SetDBNull(int ordinal); // valid for SqlDbType.Bit - void SetBoolean(SmiEventSink sink, int ordinal, bool value); + void SetBoolean(int ordinal, bool value); // valid for SqlDbType.TinyInt - void SetByte(SmiEventSink sink, int ordinal, byte value); + void SetByte(int ordinal, byte value); // Semantics for SetBytes are to modify existing value, not overwrite // Use in combination with SetLength to ensure overwriting when necessary // valid for SqlDbTypes: Binary, VarBinary, Image, Udt, Xml // (VarBinary assumed for variants) - int SetBytes(SmiEventSink sink, int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length); - void SetBytesLength(SmiEventSink sink, int ordinal, long length); + int SetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length); + void SetBytesLength(int ordinal, long length); // Semantics for SetChars are to modify existing value, not overwrite // Use in combination with SetLength to ensure overwriting when necessary // valid for character types: Char, VarChar, Text, NChar, NVarChar, NText // (NVarChar and global clr collation assumed for variants) - int SetChars(SmiEventSink sink, int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length); - void SetCharsLength(SmiEventSink sink, int ordinal, long length); + int SetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length); + void SetCharsLength(int ordinal, long length); // valid for character types: Char, VarChar, Text, NChar, NVarChar, NText - void SetString(SmiEventSink sink, int ordinal, string value, int offset, int length); + void SetString(int ordinal, string value, int offset, int length); // valid for SqlDbType.SmallInt - void SetInt16(SmiEventSink sink, int ordinal, short value); + void SetInt16(int ordinal, short value); // valid for SqlDbType.Int - void SetInt32(SmiEventSink sink, int ordinal, int value); + void SetInt32(int ordinal, int value); // valid for SqlDbType.BigInt, SqlDbType.Money, SqlDbType.SmallMoney - void SetInt64(SmiEventSink sink, int ordinal, long value); + void SetInt64(int ordinal, long value); // valid for SqlDbType.Real - void SetSingle(SmiEventSink sink, int ordinal, float value); + void SetSingle(int ordinal, float value); // valid for SqlDbType.Float - void SetDouble(SmiEventSink sink, int ordinal, double value); + void SetDouble(int ordinal, double value); // valid for SqlDbType.Numeric (uses SqlDecimal since Decimal cannot hold full range) - void SetSqlDecimal(SmiEventSink sink, int ordinal, SqlDecimal value); + void SetSqlDecimal(int ordinal, SqlDecimal value); // valid for DateTime & SmallDateTime - void SetDateTime(SmiEventSink sink, int ordinal, DateTime value); + void SetDateTime(int ordinal, DateTime value); // valid for UniqueIdentifier - void SetGuid(SmiEventSink sink, int ordinal, Guid value); + void SetGuid(int ordinal, Guid value); } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/MemoryRecordBuffer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/MemoryRecordBuffer.cs index 1daf6b225c..12dda06c9f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/MemoryRecordBuffer.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/MemoryRecordBuffer.cs @@ -28,132 +28,133 @@ internal MemoryRecordBuffer(SmiMetaData[] metaData) #region Getters // Null test // valid for all types - public override bool IsDBNull(SmiEventSink sink, int ordinal) + public override bool IsDBNull(int ordinal) { return _buffer[ordinal].IsNull; } // Check what type current sql_variant value is // valid for SqlDbType.Variant - public override SmiMetaData GetVariantType(SmiEventSink sink, int ordinal) + public override SmiMetaData GetVariantType(int ordinal) { return _buffer[ordinal].VariantType; } // valid for SqlDbType.Bit - public override bool GetBoolean(SmiEventSink sink, int ordinal) + public override bool GetBoolean(int ordinal) { return _buffer[ordinal].Boolean; } // valid for SqlDbType.TinyInt - public override byte GetByte(SmiEventSink sink, int ordinal) + public override byte GetByte(int ordinal) { return _buffer[ordinal].Byte; } // valid for SqlDbTypes: Binary, VarBinary, Image, Udt, Xml, Char, VarChar, Text, NChar, NVarChar, NText // (Character type support needed for ExecuteXmlReader handling) - public override long GetBytesLength(SmiEventSink sink, int ordinal) + public override long GetBytesLength(int ordinal) { return _buffer[ordinal].BytesLength; } - public override int GetBytes(SmiEventSink sink, int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) + public override int GetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) { return _buffer[ordinal].GetBytes(fieldOffset, buffer, bufferOffset, length); } // valid for character types: Char, VarChar, Text, NChar, NVarChar, NText - public override long GetCharsLength(SmiEventSink sink, int ordinal) + public override long GetCharsLength(int ordinal) { return _buffer[ordinal].CharsLength; } - public override int GetChars(SmiEventSink sink, int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) + public override int GetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) { return _buffer[ordinal].GetChars(fieldOffset, buffer, bufferOffset, length); } - public override string GetString(SmiEventSink sink, int ordinal) + public override string GetString(int ordinal) { return _buffer[ordinal].String; } // valid for SqlDbType.SmallInt - public override short GetInt16(SmiEventSink sink, int ordinal) + public override short GetInt16(int ordinal) { return _buffer[ordinal].Int16; } // valid for SqlDbType.Int - public override int GetInt32(SmiEventSink sink, int ordinal) + public override int GetInt32(int ordinal) { return _buffer[ordinal].Int32; } // valid for SqlDbType.BigInt, SqlDbType.Money, SqlDbType.SmallMoney - public override long GetInt64(SmiEventSink sink, int ordinal) + public override long GetInt64(int ordinal) { return _buffer[ordinal].Int64; } // valid for SqlDbType.Real - public override float GetSingle(SmiEventSink sink, int ordinal) + public override float GetSingle(int ordinal) { return _buffer[ordinal].Single; } // valid for SqlDbType.Float - public override double GetDouble(SmiEventSink sink, int ordinal) + public override double GetDouble(int ordinal) { return _buffer[ordinal].Double; } // valid for SqlDbType.Numeric (uses SqlDecimal since Decimal cannot hold full range) - public override SqlDecimal GetSqlDecimal(SmiEventSink sink, int ordinal) + public override SqlDecimal GetSqlDecimal(int ordinal) { return _buffer[ordinal].SqlDecimal; } // valid for DateTime, SmallDateTime, Date, and DateTime2 - public override DateTime GetDateTime(SmiEventSink sink, int ordinal) + public override DateTime GetDateTime(int ordinal) { return _buffer[ordinal].DateTime; } // valid for UniqueIdentifier - public override Guid GetGuid(SmiEventSink sink, int ordinal) + public override Guid GetGuid(int ordinal) { return _buffer[ordinal].Guid; } // valid for SqlDbType.Time - public override TimeSpan GetTimeSpan(SmiEventSink sink, int ordinal) + public override TimeSpan GetTimeSpan(int ordinal) { return _buffer[ordinal].TimeSpan; } // valid for DateTimeOffset - public override DateTimeOffset GetDateTimeOffset(SmiEventSink sink, int ordinal) + public override DateTimeOffset GetDateTimeOffset(int ordinal) { return _buffer[ordinal].DateTimeOffset; } #endregion #region Setters + // Set value to null // valid for all types - public override void SetDBNull(SmiEventSink sink, int ordinal) + public override void SetDBNull(int ordinal) { _buffer[ordinal].SetNull(); } // valid for SqlDbType.Bit - public override void SetBoolean(SmiEventSink sink, int ordinal, bool value) + public override void SetBoolean(int ordinal, bool value) { _buffer[ordinal].Boolean = value; } // valid for SqlDbType.TinyInt - public override void SetByte(SmiEventSink sink, int ordinal, byte value) + public override void SetByte(int ordinal, byte value) { _buffer[ordinal].Byte = value; } @@ -162,11 +163,11 @@ public override void SetByte(SmiEventSink sink, int ordinal, byte value) // Use in combination with SetLength to ensure overwriting when necessary // valid for SqlDbTypes: Binary, VarBinary, Image, Udt, Xml // (VarBinary assumed for variants) - public override int SetBytes(SmiEventSink sink, int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) + public override int SetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) { return _buffer[ordinal].SetBytes(fieldOffset, buffer, bufferOffset, length); } - public override void SetBytesLength(SmiEventSink sink, int ordinal, long length) + public override void SetBytesLength(int ordinal, long length) { _buffer[ordinal].BytesLength = length; } @@ -175,17 +176,17 @@ public override void SetBytesLength(SmiEventSink sink, int ordinal, long length) // Use in combination with SetLength to ensure overwriting when necessary // valid for character types: Char, VarChar, Text, NChar, NVarChar, NText // (NVarChar and global clr collation assumed for variants) - public override int SetChars(SmiEventSink sink, int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) + public override int SetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) { return _buffer[ordinal].SetChars(fieldOffset, buffer, bufferOffset, length); } - public override void SetCharsLength(SmiEventSink sink, int ordinal, long length) + public override void SetCharsLength(int ordinal, long length) { _buffer[ordinal].CharsLength = length; } // valid for character types: Char, VarChar, Text, NChar, NVarChar, NText - public override void SetString(SmiEventSink sink, int ordinal, string value, int offset, int length) + public override void SetString(int ordinal, string value, int offset, int length) { Debug.Assert(offset == 0 && length <= value.Length, "Invalid string length or offset"); // for sqlvariant, length could be less than value.Length @@ -193,67 +194,67 @@ public override void SetString(SmiEventSink sink, int ordinal, string value, int } // valid for SqlDbType.SmallInt - public override void SetInt16(SmiEventSink sink, int ordinal, short value) + public override void SetInt16(int ordinal, short value) { _buffer[ordinal].Int16 = value; } // valid for SqlDbType.Int - public override void SetInt32(SmiEventSink sink, int ordinal, int value) + public override void SetInt32(int ordinal, int value) { _buffer[ordinal].Int32 = value; } // valid for SqlDbType.BigInt, SqlDbType.Money, SqlDbType.SmallMoney - public override void SetInt64(SmiEventSink sink, int ordinal, long value) + public override void SetInt64(int ordinal, long value) { _buffer[ordinal].Int64 = value; } // valid for SqlDbType.Real - public override void SetSingle(SmiEventSink sink, int ordinal, float value) + public override void SetSingle(int ordinal, float value) { _buffer[ordinal].Single = value; } // valid for SqlDbType.Float - public override void SetDouble(SmiEventSink sink, int ordinal, double value) + public override void SetDouble(int ordinal, double value) { _buffer[ordinal].Double = value; } // valid for SqlDbType.Numeric (uses SqlDecimal since Decimal cannot hold full range) - public override void SetSqlDecimal(SmiEventSink sink, int ordinal, SqlDecimal value) + public override void SetSqlDecimal(int ordinal, SqlDecimal value) { _buffer[ordinal].SqlDecimal = value; } // valid for DateTime, SmallDateTime, Date, and DateTime2 - public override void SetDateTime(SmiEventSink sink, int ordinal, DateTime value) + public override void SetDateTime(int ordinal, DateTime value) { _buffer[ordinal].DateTime = value; } // valid for UniqueIdentifier - public override void SetGuid(SmiEventSink sink, int ordinal, Guid value) + public override void SetGuid(int ordinal, Guid value) { _buffer[ordinal].Guid = value; } // SqlDbType.Time - public override void SetTimeSpan(SmiEventSink sink, int ordinal, TimeSpan value) + public override void SetTimeSpan(int ordinal, TimeSpan value) { _buffer[ordinal].TimeSpan = value; } // DateTimeOffset - public override void SetDateTimeOffset(SmiEventSink sink, int ordinal, DateTimeOffset value) + public override void SetDateTimeOffset(int ordinal, DateTimeOffset value) { _buffer[ordinal].DateTimeOffset = value; } // valid for SqlDbType.Variant - public override void SetVariantMetaData(SmiEventSink sink, int ordinal, SmiMetaData metaData) + public override void SetVariantMetaData(int ordinal, SmiMetaData metaData) { _buffer[ordinal].VariantType = metaData; } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/MetadataUtilsSmi.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/MetadataUtilsSmi.cs index 42dd671431..eeda678834 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/MetadataUtilsSmi.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/MetadataUtilsSmi.cs @@ -19,7 +19,7 @@ namespace Microsoft.Data.SqlClient.Server /// These are all based off of knowing the clr type of the value /// as an ExtendedClrTypeCode enum for rapid access. /// - internal class MetaDataUtilsSmi + internal static class MetaDataUtilsSmi { internal const SqlDbType InvalidSqlDbType = (SqlDbType)(-1); internal const long InvalidMaxLength = -2; @@ -163,10 +163,6 @@ internal static bool IsAnsiType(SqlDbType type) => type == SqlDbType.Char || type == SqlDbType.VarChar || type == SqlDbType.Text; - internal static bool IsBinaryType(SqlDbType type) => type == SqlDbType.Binary || - type == SqlDbType.VarBinary || - type == SqlDbType.Image; - // Does this type use PLP format values? internal static bool IsPlpFormat(SmiMetaData metaData) => metaData.MaxLength == SmiMetaData.UnlimitedMaxLengthIndicator || @@ -187,14 +183,10 @@ internal static bool IsPlpFormat(SmiMetaData metaData) => // must instantiate a Type object. The typecode switch also degenerates into a large if-then-else for // all but the primitive clr types. internal static ExtendedClrTypeCode DetermineExtendedTypeCodeForUseWithSqlDbType( - SqlDbType dbType, - bool isMultiValued, - object value, - Type udtType -#if NETFRAMEWORK - ,ulong smiVersion -#endif - ) + SqlDbType dbType, + bool isMultiValued, + object value, + Type udtType) { ExtendedClrTypeCode extendedCode = ExtendedClrTypeCode.Invalid; @@ -266,15 +258,8 @@ Type udtType break; #endif - case SqlDbType.DateTime2: -#if NETFRAMEWORK - if (smiVersion >= SmiContextFactory.Sql2008Version) - { - goto case SqlDbType.DateTime; - } - break; -#endif case SqlDbType.DateTime: + case SqlDbType.DateTime2: case SqlDbType.SmallDateTime: if (value.GetType() == typeof(DateTime)) extendedCode = ExtendedClrTypeCode.DateTime; @@ -361,20 +346,12 @@ Type udtType break; #else case SqlDbType.Time: - if (value.GetType() == typeof(TimeSpan) -#if NETFRAMEWORK - && smiVersion >= SmiContextFactory.Sql2008Version -#endif - ) + if (value.GetType() == typeof(TimeSpan)) extendedCode = ExtendedClrTypeCode.TimeSpan; break; #endif case SqlDbType.DateTimeOffset: - if (value.GetType() == typeof(DateTimeOffset) -#if NETFRAMEWORK - && smiVersion >= SmiContextFactory.Sql2008Version -#endif - ) + if (value.GetType() == typeof(DateTimeOffset)) extendedCode = ExtendedClrTypeCode.DateTimeOffset; break; case SqlDbType.Xml: @@ -985,27 +962,5 @@ internal static SmiExtendedMetaData SmiMetaDataFromSchemaTableRow(DataRow schema null, null); } - -#if NETFRAMEWORK - - static internal bool IsValidForSmiVersion(SmiExtendedMetaData md, ulong smiVersion) - { - if (SmiContextFactory.LatestVersion == smiVersion) - { - return true; - } - else - { - // 2005 doesn't support Structured nor the new time types - Debug.Assert(SmiContextFactory.Sql2005Version == smiVersion, "Other versions should have been eliminated during link stage"); - return md.SqlDbType != SqlDbType.Structured && - md.SqlDbType != SqlDbType.Date && - md.SqlDbType != SqlDbType.DateTime2 && - md.SqlDbType != SqlDbType.DateTimeOffset && - md.SqlDbType != SqlDbType.Time; - } - } - -#endif } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiEventSink.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiEventSink.cs deleted file mode 100644 index a7de4b1947..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiEventSink.cs +++ /dev/null @@ -1,130 +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. - -namespace Microsoft.Data.SqlClient.Server -{ - /// - /// SqlEventSink is implemented by calling code. In all methods that accept - /// a SqlEventSink directly the sink must be able to handle multiple callbacks - /// without control returning from the original call. - /// - /// Methods that do not accept SmiEventSync are (generally) ProcessEvent on - /// the SmiEventStream methods returning a SmiEventStream and methods that - /// are certain to never call to the server (most will, for in-proc back end). - /// - /// Methods are commented with their corresponding TDS token - /// - /// NOTE: Throwing from these methods will not usually produce the desired - /// effect -- the managed to native boundary will eat any exceptions, - /// and will cause a simple "Something bad happened" exception to be - /// thrown in the native to managed boundary... - /// - internal abstract class SmiEventSink - { -#if NETFRAMEWORK - - // Called at end of stream whether errors or no - internal abstract void BatchCompleted(); - - // Called zero or one time when output parameters are available (errors could prevent event from occuring) - internal virtual void ParameterAvailable(SmiParameterMetaData metaData, SmiTypedGetterSetter paramValue, int ordinal) - { - // Adding as of V200 - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3- and hasn't implemented V200 yet. - // 2) Server didn't implement V200 on some interface, but negotiated V200+. - Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - // Called when the server database context changes (ENVCHANGE token) - internal abstract void DefaultDatabaseChanged(string databaseName); - - // Called for messages and errors (ERROR and INFO tokens) - internal abstract void MessagePosted(int number, byte state, byte errorClass, string server, string message, string procedure, int lineNumber); - - // Called for new resultset starting (COLMETADATA token) - internal abstract void MetaDataAvailable(SmiQueryMetaData[] metaData, bool nextEventIsRow); - - - internal virtual void RowAvailable(SmiTypedGetterSetter rowData) - { - // Adding as of V200 - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3- and hasn't implemented V200 yet. - // 2) Server didn't implement V200 on some interface, but negotiated V200+. - Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - // Called when any statement completes on server (DONE token) - internal abstract void StatementCompleted(int rowsAffected); - - // Called when a transaction is committed (ENVCHANGE token) - internal abstract void TransactionCommitted(long transactionId); - - // Called when a transaction is committed (ENVCHANGE token) - internal abstract void TransactionDefected(long transactionId); - - // Called when a transaction is committed (ENVCHANGE token) - internal abstract void TransactionEnlisted(long transactionId); - - // Called when a transaction is forcibly ended in the server, not requested - // by the provider's batch (ENVCHANGE token) - internal abstract void TransactionEnded(long transactionId); - - // Called when a transaction is rolled back (ENVCHANGE token) - internal abstract void TransactionRolledBack(long transactionId); - - // Called when a transaction is started (ENVCHANGE token) - internal abstract void TransactionStarted(long transactionId); - - -#region OBSOLETE METHODS -#region OBSOLETED as of V200 but active in previous version - // Called zero or one time when output parameters are available (errors could prevent event from occuring) - internal virtual void ParametersAvailable(SmiParameterMetaData[] metaData, ITypedGettersV3 paramValues) - { - // Adding as of V3 - // Obsoleting as of V200 - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V200+ and dropped support for V200-. - // 2) Server didn't implement V3- on some interface and negotiated V3-. - Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - // Called when a new row arrives (ROW token) - internal virtual void RowAvailable(ITypedGettersV3 rowData) - { - // Adding as of V3 - // Obsoleting as of V200 - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V200+ and dropped support for V200-. - // 2) Server didn't implement V3- on some interface and negotiated V3-. - Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - -#endregion - -#region OBSOLETED and never shipped (without ObsoleteAttribute) - // Called when a new row arrives (ROW token) - internal virtual void RowAvailable(ITypedGetters rowData) - { - // Obsoleting from SMI -- use end of dispose that takes an event sink instead. - // Intended to be removed (along with inheriting IDisposable) prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - Microsoft.Data.Common.ADP.InternalError(Microsoft.Data.Common.ADP.InternalErrorCode.UnimplementedSMIMethod); - } - -#endregion -#endregion - -#endif - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiEventSink_Default.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiEventSink_Default.cs deleted file mode 100644 index 0477b6aea4..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiEventSink_Default.cs +++ /dev/null @@ -1,149 +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.Diagnostics; - -namespace Microsoft.Data.SqlClient.Server -{ - internal partial class SmiEventSink_Default : SmiEventSink - { - private SqlErrorCollection _errors; - private SqlErrorCollection _warnings; - - internal virtual string ServerVersion => null; - - internal SmiEventSink_Default() - { - } - - internal bool HasMessages - { - get - { -#if NETFRAMEWORK - SmiEventSink_Default parent = (SmiEventSink_Default)_parent; - if (parent != null) - { - return parent.HasMessages; - } - else -#endif - { - bool result = _errors != null || _warnings != null; - return result; - } - } - } - - protected virtual void DispatchMessages( -#if NETFRAMEWORK - bool ignoreNonFatalMessages -#endif - ) - { - // virtual because we want a default implementation in the cases - // where we don't have a connection to process stuff, but we want to - // provide the connection the ability to fire info messages when it - // hooks up. -#if NETFRAMEWORK - SmiEventSink_Default parent = (SmiEventSink_Default)_parent; - if (parent != null) - { - parent.DispatchMessages(ignoreNonFatalMessages); - } - else -#endif - { - SqlException errors = ProcessMessages(true -#if NETFRAMEWORK - , ignoreNonFatalMessages -#endif - ); // ignore warnings, because there's no place to send them... - if (errors != null) - { - throw errors; - } - } - - } - - protected SqlException ProcessMessages(bool ignoreWarnings -#if NETFRAMEWORK - , bool ignoreNonFatalMessages -#endif - ) - { - SqlException result = null; - SqlErrorCollection temp = null; // temp variable to store that which is being thrown - so that local copies can be deleted - - if (_errors != null) - { - Debug.Assert(0 != _errors.Count, "empty error collection?"); // must be something in the collection -#if NETFRAMEWORK - if (ignoreNonFatalMessages) - { - temp = new SqlErrorCollection(); - foreach (SqlError error in _errors) - { - if (error.Class >= TdsEnums.FATAL_ERROR_CLASS) - { - temp.Add(error); - } - } - if (temp.Count <= 0) - { - temp = null; - } - } - else -#endif - { - if (_warnings != null) - { - // When we throw an exception we place all the warnings that - // occurred at the end of the collection - after all the errors. - // That way the user can see all the errors AND warnings that - // occurred for the exception. - foreach (SqlError warning in _warnings) - { - _errors.Add(warning); - } - } - temp = _errors; - } - - _errors = null; - _warnings = null; - } - else - { - Debug.Assert(_warnings == null || 0 != _warnings.Count, "empty warning collection?");// must be something in the collection - - if (!ignoreWarnings) - { - temp = _warnings; - } - _warnings = null; - } - - if (temp != null) - { - result = SqlException.CreateException(temp, ServerVersion); - } - return result; - } - - internal void ProcessMessagesAndThrow() - { -#if NETFRAMEWORK - ProcessMessagesAndThrow(false); -#else - if (HasMessages) - { - DispatchMessages(); - } -#endif - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiEventSink_Default.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiEventSink_Default.netfx.cs deleted file mode 100644 index 9872953b24..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiEventSink_Default.netfx.cs +++ /dev/null @@ -1,261 +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.Diagnostics; - -namespace Microsoft.Data.SqlClient.Server -{ - internal partial class SmiEventSink_Default : SmiEventSink - { - private SmiEventSink _parent; // next level up, which we'll defer to if we don't need to handle the event. - private SqlErrorCollection Errors - { - get - { - if (_errors == null) - { - _errors = new SqlErrorCollection(); - } - - return _errors; - } - } - - internal SmiEventSink Parent - { - get => _parent; - set => _parent = value; - } - - private SqlErrorCollection Warnings - { - get - { - if (_warnings == null) - { - _warnings = new SqlErrorCollection(); - } - - return _warnings; - } - } - - internal void ProcessMessagesAndThrow(bool ignoreNonFatalMessages) - { - if (HasMessages) - { - DispatchMessages(ignoreNonFatalMessages); - } - } - - internal void CleanMessages() - { - SmiEventSink_Default parent = (SmiEventSink_Default)_parent; - if (parent != null) - { - parent.CleanMessages(); - } - else - { - _errors = null; - _warnings = null; - } - } - - internal enum UnexpectedEventType - { - BatchCompleted, - ColumnInfoAvailable, - DefaultDatabaseChanged, - MessagePosted, - MetaDataAvailable, - ParameterAvailable, - ParametersAvailable, - RowAvailable, - StatementCompleted, - TableNameAvailable, - TransactionCommitted, - TransactionDefected, - TransactionEnlisted, - TransactionEnded, - TransactionRolledBack, - TransactionStarted, - } - - internal SmiEventSink_Default(SmiEventSink parent) - { - _parent = parent; - } - - // - //NOTE: See the note in SmiEventSink about throwing from these methods; - // We're throwing here because we don't want to miss something, but - //you'll need to turn on Bid tracing to figure out what it is that - //was thrown, because they will be eaten by the server and replaced - //with a different exception. - // Called at end of stream - // - internal override void BatchCompleted() - { - if (_parent == null) - { - throw SQL.UnexpectedSmiEvent(UnexpectedEventType.BatchCompleted); - } - _parent.BatchCompleted(); - } - - internal override void ParametersAvailable(SmiParameterMetaData[] metaData, ITypedGettersV3 paramValues) - { - if (_parent == null) - { - throw SQL.UnexpectedSmiEvent(UnexpectedEventType.ParametersAvailable); - } - _parent.ParametersAvailable(metaData, paramValues); - } - - internal override void ParameterAvailable(SmiParameterMetaData metaData, SmiTypedGetterSetter paramValue, int ordinal) - { - if (_parent == null) - { - throw SQL.UnexpectedSmiEvent(UnexpectedEventType.ParameterAvailable); - } - _parent.ParameterAvailable(metaData, paramValue, ordinal); - } - - // Called when the server database context changes (ENVCHANGE token) - internal override void DefaultDatabaseChanged(string databaseName) - { - if (_parent == null) - { - throw SQL.UnexpectedSmiEvent(UnexpectedEventType.DefaultDatabaseChanged); - } - _parent.DefaultDatabaseChanged(databaseName); - } - - // Called for messages and errors (ERROR and INFO tokens) - internal override void MessagePosted(int number, byte state, byte errorClass, string server, string message, string procedure, int lineNumber) - { - if (_parent == null) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, number={1} state={2} errorClass={3} server='{4}' message='{5}' procedure='{6}' linenumber={7}.", 0, number, state, errorClass, server, message, procedure, lineNumber); - SqlError error = new SqlError(number, state, errorClass, server, message, procedure, lineNumber); - - if (error.Class < TdsEnums.MIN_ERROR_CLASS) - { - Warnings.Add(error); - } - else - { - Errors.Add(error); - } - } - else - { - _parent.MessagePosted(number, state, errorClass, server, message, procedure, lineNumber); - } - } - - // Called for new resultset starting (COLMETADATA token) - internal override void MetaDataAvailable(SmiQueryMetaData[] metaData, bool nextEventIsRow) - { - if (_parent == null) - { - throw SQL.UnexpectedSmiEvent(UnexpectedEventType.MetaDataAvailable); - } - _parent.MetaDataAvailable(metaData, nextEventIsRow); - } - - // Called when a new row arrives (ROW token) - internal override void RowAvailable(ITypedGetters rowData) - { - if (_parent == null) - { - throw SQL.UnexpectedSmiEvent(UnexpectedEventType.RowAvailable); - } - _parent.RowAvailable(rowData); - } - - // Called when a new row arrives (ROW token) - internal override void RowAvailable(ITypedGettersV3 rowData) - { - if (_parent == null) - { - throw SQL.UnexpectedSmiEvent(UnexpectedEventType.RowAvailable); - } - _parent.RowAvailable(rowData); - } - - // Called when any statement completes on server (DONE token) - internal override void StatementCompleted(int rowsAffected) - { - if (_parent == null) - { - throw SQL.UnexpectedSmiEvent(UnexpectedEventType.StatementCompleted); - } - _parent.StatementCompleted(rowsAffected); - } - - // Called when a transaction is committed (ENVCHANGE token) - internal override void TransactionCommitted(long transactionId) - { - if (_parent == null) - { - throw SQL.UnexpectedSmiEvent(UnexpectedEventType.TransactionCommitted); - } - _parent.TransactionCommitted(transactionId); - } - - // Called when a transaction is committed (ENVCHANGE token) - internal override void TransactionDefected(long transactionId) - { - if (_parent == null) - { - throw SQL.UnexpectedSmiEvent(UnexpectedEventType.TransactionDefected); - } - _parent.TransactionDefected(transactionId); - } - - // Called when a transaction is committed (ENVCHANGE token) - internal override void TransactionEnlisted(long transactionId) - { - if (_parent == null) - { - throw SQL.UnexpectedSmiEvent(UnexpectedEventType.TransactionEnlisted); - } - _parent.TransactionEnlisted(transactionId); - } - - // Called when a transaction is forcibly ended in the server, not requested - // by the provider's batch (ENVCHANGE token) - internal override void TransactionEnded(long transactionId) - { - if (_parent == null) - { - throw SQL.UnexpectedSmiEvent(UnexpectedEventType.TransactionEnded); - } - _parent.TransactionEnded(transactionId); - } - - // Called when a transaction is rolled back (ENVCHANGE token) - internal override void TransactionRolledBack(long transactionId) - { - if (_parent == null) - { - throw SQL.UnexpectedSmiEvent(UnexpectedEventType.TransactionRolledBack); - } - _parent.TransactionRolledBack(transactionId); - } - - // Called when a transaction is started (ENVCHANGE token) - internal override void TransactionStarted(long transactionId) - { - if (_parent == null) - { - throw SQL.UnexpectedSmiEvent(UnexpectedEventType.TransactionStarted); - } - _parent.TransactionStarted(transactionId); - } - } -} - diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiGettersStream.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiGettersStream.cs index 84378027e9..fd38de350a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiGettersStream.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiGettersStream.cs @@ -9,20 +9,17 @@ namespace Microsoft.Data.SqlClient.Server { internal class SmiGettersStream : Stream { - private SmiEventSink_Default _sink; private ITypedGettersV3 _getters; private int _ordinal; private long _readPosition; private SmiMetaData _metaData; - internal SmiGettersStream(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal SmiGettersStream(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { - Debug.Assert(sink != null); Debug.Assert(getters != null); Debug.Assert(0 <= ordinal); Debug.Assert(metaData != null); - _sink = sink; _getters = getters; _ordinal = ordinal; _readPosition = 0; @@ -58,7 +55,7 @@ public override long Length { get { - return ValueUtilsSmi.GetBytesInternal(_sink, _getters, _ordinal, _metaData, 0, null, 0, 0, false); + return ValueUtilsSmi.GetBytesInternal(_getters, _ordinal, _metaData, 0, null, 0, 0, false); } } @@ -91,7 +88,7 @@ public override void SetLength(long value) public override int Read(byte[] buffer, int offset, int count) { - long bytesRead = ValueUtilsSmi.GetBytesInternal(_sink, _getters, _ordinal, _metaData, _readPosition, buffer, offset, count, false); + long bytesRead = ValueUtilsSmi.GetBytesInternal(_getters, _ordinal, _metaData, _readPosition, buffer, offset, count, false); _readPosition += bytesRead; return checked((int)bytesRead); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiMetaData.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiMetaData.cs index a94ada1422..220471394c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiMetaData.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiMetaData.cs @@ -64,10 +64,6 @@ internal class SmiMetaData internal const int MinPrecision = 1; // SqlDecimal defines max precision internal const int MinScale = 0; // SqlDecimal defines max scale internal const int MaxTimeScale = 7; // Max scale for time, datetime2, and datetimeoffset - internal static readonly DateTime MaxSmallDateTime = new DateTime(2079, 06, 06, 23, 59, 29, 998); - internal static readonly DateTime MinSmallDateTime = new DateTime(1899, 12, 31, 23, 59, 29, 999); - internal static readonly SqlMoney MaxSmallMoney = new SqlMoney(((decimal)int.MaxValue) / 10000); - internal static readonly SqlMoney MinSmallMoney = new SqlMoney(((decimal)int.MinValue) / 10000); internal const SqlCompareOptions DefaultStringCompareOptions = SqlCompareOptions.IgnoreCase | SqlCompareOptions.IgnoreKanaType | SqlCompareOptions.IgnoreWidth; @@ -116,41 +112,6 @@ internal class SmiMetaData internal static readonly SmiMetaData DefaultDateTimeOffset = new SmiMetaData(SqlDbType.DateTimeOffset, 10, 0, 7, SqlCompareOptions.None); // SqlDbType.DateTimeOffset // No default for generic UDT - // character defaults hook thread-local culture to get collation - internal static SmiMetaData DefaultChar => - new SmiMetaData( - DefaultChar_NoCollation.SqlDbType, - DefaultChar_NoCollation.MaxLength, - DefaultChar_NoCollation.Precision, - DefaultChar_NoCollation.Scale, - CultureInfo.CurrentCulture.LCID, - SqlCompareOptions.IgnoreCase | SqlCompareOptions.IgnoreKanaType | SqlCompareOptions.IgnoreWidth, - null - ); - - - internal static SmiMetaData DefaultNChar => - new SmiMetaData( - DefaultNChar_NoCollation.SqlDbType, - DefaultNChar_NoCollation.MaxLength, - DefaultNChar_NoCollation.Precision, - DefaultNChar_NoCollation.Scale, - CultureInfo.CurrentCulture.LCID, - SqlCompareOptions.IgnoreCase | SqlCompareOptions.IgnoreKanaType | SqlCompareOptions.IgnoreWidth, - null - ); - - internal static SmiMetaData DefaultNText => - new SmiMetaData( - DefaultNText_NoCollation.SqlDbType, - DefaultNText_NoCollation.MaxLength, - DefaultNText_NoCollation.Precision, - DefaultNText_NoCollation.Scale, - CultureInfo.CurrentCulture.LCID, - SqlCompareOptions.IgnoreCase | SqlCompareOptions.IgnoreKanaType | SqlCompareOptions.IgnoreWidth, - null - ); - internal static SmiMetaData DefaultNVarChar => new SmiMetaData( DefaultNVarChar_NoCollation.SqlDbType, @@ -162,28 +123,6 @@ internal class SmiMetaData null ); - internal static SmiMetaData DefaultText => - new SmiMetaData( - DefaultText_NoCollation.SqlDbType, - DefaultText_NoCollation.MaxLength, - DefaultText_NoCollation.Precision, - DefaultText_NoCollation.Scale, - CultureInfo.CurrentCulture.LCID, - SqlCompareOptions.IgnoreCase | SqlCompareOptions.IgnoreKanaType | SqlCompareOptions.IgnoreWidth, - null - ); - - internal static SmiMetaData DefaultVarChar => - new SmiMetaData( - DefaultVarChar_NoCollation.SqlDbType, - DefaultVarChar_NoCollation.MaxLength, - DefaultVarChar_NoCollation.Precision, - DefaultVarChar_NoCollation.Scale, - CultureInfo.CurrentCulture.LCID, - SqlCompareOptions.IgnoreCase | SqlCompareOptions.IgnoreKanaType | SqlCompareOptions.IgnoreWidth, - null - ); - // The one and only constructor for use by outside code. // // Parameters that matter for given values of dbType (other parameters are ignored in favor of internal defaults). @@ -413,63 +352,6 @@ SmiMetaDataPropertyCollection extendedProperties #endif } - internal bool IsValidMaxLengthForCtorGivenType(SqlDbType dbType, long maxLength) - { - bool result = true; - switch (dbType) - { - case SqlDbType.BigInt: - case SqlDbType.Bit: - case SqlDbType.DateTime: - case SqlDbType.Float: - case SqlDbType.Image: - case SqlDbType.Int: - case SqlDbType.Money: - case SqlDbType.Real: - case SqlDbType.SmallDateTime: - case SqlDbType.SmallInt: - case SqlDbType.SmallMoney: - case SqlDbType.Timestamp: - case SqlDbType.TinyInt: - case SqlDbType.UniqueIdentifier: - case SqlDbType.Variant: - case SqlDbType.Xml: - case SqlDbType.NText: - case SqlDbType.Text: - case SqlDbType.Decimal: - case SqlDbType.Udt: - case SqlDbType.Structured: - case SqlDbType.Date: - case SqlDbType.Time: - case SqlDbType.DateTime2: - case SqlDbType.DateTimeOffset: - break; - case SqlDbType.Binary: - result = 0 < maxLength && MaxBinaryLength >= maxLength; - break; - case SqlDbType.VarBinary: - result = UnlimitedMaxLengthIndicator == maxLength || (0 < maxLength && MaxBinaryLength >= maxLength); - break; - case SqlDbType.Char: - result = 0 < maxLength && MaxANSICharacters >= maxLength; - break; - case SqlDbType.NChar: - result = 0 < maxLength && MaxUnicodeCharacters >= maxLength; - break; - case SqlDbType.NVarChar: - result = UnlimitedMaxLengthIndicator == maxLength || (0 < maxLength && MaxUnicodeCharacters >= maxLength); - break; - case SqlDbType.VarChar: - result = UnlimitedMaxLengthIndicator == maxLength || (0 < maxLength && MaxANSICharacters >= maxLength); - break; - default: - Debug.Fail("How in the world did we get here? :" + dbType); - break; - } - - return result; - } - // Sql-style compare options for character types. internal SqlCompareOptions CompareOptions => _compareOptions; @@ -501,57 +383,6 @@ internal Type Type } } - // Clr Type instance for user-defined types in cases where we don't want to throw if the assembly isn't available - internal Type TypeWithoutThrowing - { - get - { - // Fault-in UDT clr types on access if have assembly-qualified name - if (_clrType == null && SqlDbType.Udt == _databaseType && _udtAssemblyQualifiedName != null) - { - _clrType = Type.GetType(_udtAssemblyQualifiedName, false); - } - return _clrType; - } - } - - internal string TypeName - { - get - { - string result; - if (SqlDbType.Udt == _databaseType) - { - Debug.Assert(string.Empty == s_typeNameByDatabaseType[(int)_databaseType], "unexpected udt?"); - result = Type.FullName; - } - else - { - result = s_typeNameByDatabaseType[(int)_databaseType]; - Debug.Assert(result != null, "unknown type name?"); - } - return result; - } - } - - internal string AssemblyQualifiedName - { - get - { - string result = null; - if (SqlDbType.Udt == _databaseType) - { - // Fault-in assembly-qualified name if type is available - if (_udtAssemblyQualifiedName == null && _clrType != null) - { - _udtAssemblyQualifiedName = _clrType.AssemblyQualifiedName; - } - result = _udtAssemblyQualifiedName; - } - return result; - } - } - internal bool IsMultiValued => _isMultiValued; // Returns read-only list of field metadata @@ -699,8 +530,6 @@ private void SetDefaultsForType(SqlDbType dbType) _extendedProperties = smdDflt._extendedProperties; // This is ok due to immutability } - internal string TraceString() => TraceString(0); - virtual internal string TraceString(int indent) { string indentStr = new string(' ', indent); @@ -1221,8 +1050,6 @@ bool isColumnSet internal bool IsIdentity => _isIdentity; - internal bool IsColumnSet => _isColumnSet; - internal override string TraceString(int indent) { return string.Format(CultureInfo.InvariantCulture, "{0}" @@ -1260,65 +1087,6 @@ internal class SmiQueryMetaData : SmiStorageMetaData private readonly SqlBoolean _isAliased; private readonly SqlBoolean _isHidden; - internal SmiQueryMetaData( - SqlDbType dbType, - long maxLength, - byte precision, - byte scale, - long localeId, - SqlCompareOptions compareOptions, -#if NET - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] -#endif - Type userDefinedType, - string name, - string typeSpecificNamePart1, - string typeSpecificNamePart2, - string typeSpecificNamePart3, - bool allowsDBNull, - string serverName, - string catalogName, - string schemaName, - string tableName, - string columnName, - SqlBoolean isKey, - bool isIdentity, - bool isReadOnly, - SqlBoolean isExpression, - SqlBoolean isAliased, - SqlBoolean isHidden - ) - : this( - dbType, - maxLength, - precision, - scale, - localeId, - compareOptions, - userDefinedType, - false, - null, - null, - name, - typeSpecificNamePart1, - typeSpecificNamePart2, - typeSpecificNamePart3, - allowsDBNull, - serverName, - catalogName, - schemaName, - tableName, - columnName, - isKey, - isIdentity, - isReadOnly, - isExpression, - isAliased, - isHidden - ) - { - } - // SMI V200 ctor. internal SmiQueryMetaData( SqlDbType dbType, @@ -1451,8 +1219,6 @@ SqlBoolean isHidden _isHidden = isHidden; } - internal bool IsReadOnly => _isReadOnly; - internal SqlBoolean IsExpression => _isExpression; internal SqlBoolean IsAliased => _isAliased; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiRecordBuffer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiRecordBuffer.cs index a0524dd179..ffb1f8743a 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiRecordBuffer.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiRecordBuffer.cs @@ -2,860 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Data; -using System.Data.SqlTypes; -using Microsoft.Data.Common; - namespace Microsoft.Data.SqlClient.Server { // Class for implementing a record object that could take advantage of the // environment available to a particular protocol level (such as storing data // in native structures for in-proc data access). Includes methods to send // the record to a context pipe (useful for in-proc scenarios). - internal abstract class SmiRecordBuffer : SmiTypedGetterSetter, ITypedGettersV3, ITypedSettersV3, ITypedGetters, ITypedSetters, IDisposable + internal abstract class SmiRecordBuffer : SmiTypedGetterSetter { - #region SMI active methods as of V200 - - - #region Supported access method types (Get] vs. Set) - // SmiRecordBuffer defaults both CanGet and CanSet to true to support - // already-shipped SMIV3 record buffer classes. Sub-classes are free to override. - internal override bool CanGet - { - get - { - return true; - } - } - - internal override bool CanSet - { - get - { - return true; - } - } - #endregion - - - - #region Value getters - - // SmiRecordBuffer subclasses that expose CanGet == true must implement all Getters from SmiTypedGetterSetter - // SmiRecordBuffer itself does not need to implement these, since it inherits the default implementation from - // SmiTypedGetterSetter - - #endregion - - #region Value setters - - // SmiRecordBuffer subclasses that expose CanSet == true must implement all Setters from SmiTypedGetterSetter - // SmiRecordBuffer itself does not need to implement these, since it inherits the default implementation from - // SmiTypedGetterSetter - - #endregion - - #endregion - - #region OBSOLETE STUFF than never shipped without obsolete attribute - - // - // IDisposable - // - public virtual void Dispose() - { - // ******** OBSOLETING from SMI -- use Close instead - // Intended to be removed (along with inheriting IDisposable) prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - // - // ITypedGetters methods - // - public virtual bool IsDBNull(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlDbType GetVariantType(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual bool GetBoolean(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual byte GetByte(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual long GetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual char GetChar(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual long GetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual short GetInt16(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual int GetInt32(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual long GetInt64(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual float GetFloat(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual double GetDouble(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual string GetString(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual decimal GetDecimal(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual DateTime GetDateTime(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual Guid GetGuid(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlBoolean GetSqlBoolean(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlByte GetSqlByte(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlInt16 GetSqlInt16(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlInt32 GetSqlInt32(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlInt64 GetSqlInt64(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlSingle GetSqlSingle(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlDouble GetSqlDouble(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlMoney GetSqlMoney(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlDateTime GetSqlDateTime(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlDecimal GetSqlDecimal(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlString GetSqlString(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlBinary GetSqlBinary(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlGuid GetSqlGuid(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlChars GetSqlChars(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlBytes GetSqlBytes(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlXml GetSqlXml(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlXml GetSqlXmlRef(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlBytes GetSqlBytesRef(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual SqlChars GetSqlCharsRef(int ordinal) - { - // ******** OBSOLETING from SMI -- use corresponding ITypedGettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - // - // ITypedSetters methods - // - public virtual void SetDBNull(int ordinal) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetBoolean(int ordinal, bool value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetByte(int ordinal, byte value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetChar(int ordinal, char value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetInt16(int ordinal, short value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetInt32(int ordinal, int value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetInt64(int ordinal, long value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetFloat(int ordinal, float value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetDouble(int ordinal, double value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetString(int ordinal, string value) - { - // Implemented as empty virtual method to allow transport to remove it's implementation - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V2 and dropped support for V1. - // 2) Server didn't implement V1 on some interface and negotiated V1. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetString(int ordinal, string value, int offset) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetDecimal(int ordinal, decimal value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetDateTime(int ordinal, DateTime value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetGuid(int ordinal, Guid value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlBoolean(int ordinal, SqlBoolean value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlByte(int ordinal, SqlByte value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlInt16(int ordinal, SqlInt16 value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlInt32(int ordinal, SqlInt32 value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlInt64(int ordinal, SqlInt64 value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlSingle(int ordinal, SqlSingle value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlDouble(int ordinal, SqlDouble value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlMoney(int ordinal, SqlMoney value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlDateTime(int ordinal, SqlDateTime value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlDecimal(int ordinal, SqlDecimal value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlString(int ordinal, SqlString value) - { - // Implemented as empty virtual method to allow transport to remove it's implementation - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V2 and dropped support for V1. - // 2) Server didn't implement V1 on some interface and negotiated V1. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlString(int ordinal, SqlString value, int offset) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlBinary(int ordinal, SqlBinary value) - { - // Implemented as empty virtual method to allow transport to remove it's implementation - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V2 and dropped support for V1. - // 2) Server didn't implement V1 on some interface and negotiated V1. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlBinary(int ordinal, SqlBinary value, int offset) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlGuid(int ordinal, SqlGuid value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlChars(int ordinal, SqlChars value) - { - // Implemented as empty virtual method to allow transport to remove it's implementation - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V2 and dropped support for V1. - // 2) Server didn't implement V1 on some interface and negotiated V1. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlChars(int ordinal, SqlChars value, int offset) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlBytes(int ordinal, SqlBytes value) - { - // Implemented as empty virtual method to allow transport to remove it's implementation - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V2 and dropped support for V1. - // 2) Server didn't implement V1 on some interface and negotiated V1. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlBytes(int ordinal, SqlBytes value, int offset) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. - - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - - public virtual void SetSqlXml(int ordinal, SqlXml value) - { - // ******** OBSOLETING from SMI -- use ITypedSettersV3 method instead - // Intended to be removed prior to RTM. + // already-shipped SMIV3 record buffer classes. Sub-classes are free to override. + protected override bool CanGet => true; - // Implement body with throw because there are only a couple of ways to get to this code: - // 1) Client is calling this method even though the server negotiated for V3+ and dropped support for V2-. - // 2) Server didn't implement V2- on some interface and negotiated V2-. - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - #endregion + protected override bool CanSet => true; } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiSettersStream.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiSettersStream.cs index 513d36f942..bd47a9089f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiSettersStream.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiSettersStream.cs @@ -10,20 +10,17 @@ namespace Microsoft.Data.SqlClient.Server { internal class SmiSettersStream : Stream { - private SmiEventSink_Default _sink; private ITypedSettersV3 _setters; private int _ordinal; private long _lengthWritten; private SmiMetaData _metaData; - internal SmiSettersStream(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData) + internal SmiSettersStream(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData) { - Debug.Assert(sink != null); Debug.Assert(setters != null); Debug.Assert(0 <= ordinal); Debug.Assert(metaData != null); - _sink = sink; _setters = setters; _ordinal = ordinal; _lengthWritten = 0; @@ -77,7 +74,7 @@ public override long Position public override void Flush() { - _lengthWritten = ValueUtilsSmi.SetBytesLength(_sink, _setters, _ordinal, _metaData, _lengthWritten); + _lengthWritten = ValueUtilsSmi.SetBytesLength(_setters, _ordinal, _metaData, _lengthWritten); } public override long Seek(long offset, SeekOrigin origin) @@ -91,7 +88,7 @@ public override void SetLength(long value) { throw ADP.ArgumentOutOfRange(nameof(value)); } - ValueUtilsSmi.SetBytesLength(_sink, _setters, _ordinal, _metaData, value); + ValueUtilsSmi.SetBytesLength(_setters, _ordinal, _metaData, value); } public override int Read(byte[] buffer, int offset, int count) @@ -101,7 +98,7 @@ public override int Read(byte[] buffer, int offset, int count) public override void Write(byte[] buffer, int offset, int count) { - _lengthWritten += ValueUtilsSmi.SetBytes(_sink, _setters, _ordinal, _metaData, _lengthWritten, buffer, offset, count); + _lengthWritten += ValueUtilsSmi.SetBytes(_setters, _ordinal, _metaData, _lengthWritten, buffer, offset, count); } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiTypedGetterSetter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiTypedGetterSetter.cs index 00729cbecf..f12fee9631 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiTypedGetterSetter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiTypedGetterSetter.cs @@ -14,23 +14,23 @@ namespace Microsoft.Data.SqlClient.Server internal abstract class SmiTypedGetterSetter : ITypedGettersV3, ITypedSettersV3 { #region Read/Write - // Are calls to Get methods allowed? - internal abstract bool CanGet - { - get; - } - - // Are calls to Set methods allowed? - internal abstract bool CanSet - { - get; - } + + /// + /// Are calls to Get methods allowed? + /// + protected abstract bool CanGet { get; } + + /// + /// Are calls to Set methods allowed? + /// + protected abstract bool CanSet { get; } + #endregion #region Getters // Null test // valid for all types - public virtual bool IsDBNull(SmiEventSink sink, int ordinal) + public virtual bool IsDBNull(int ordinal) { if (!CanGet) { @@ -44,7 +44,7 @@ public virtual bool IsDBNull(SmiEventSink sink, int ordinal) // Check what type current sql_variant value is // valid for SqlDbType.Variant - public virtual SmiMetaData GetVariantType(SmiEventSink sink, int ordinal) + public virtual SmiMetaData GetVariantType(int ordinal) { if (!CanGet) { @@ -57,7 +57,7 @@ public virtual SmiMetaData GetVariantType(SmiEventSink sink, int ordinal) } // valid for SqlDbType.Bit - public virtual bool GetBoolean(SmiEventSink sink, int ordinal) + public virtual bool GetBoolean(int ordinal) { if (!CanGet) { @@ -70,7 +70,7 @@ public virtual bool GetBoolean(SmiEventSink sink, int ordinal) } // valid for SqlDbType.TinyInt - public virtual byte GetByte(SmiEventSink sink, int ordinal) + public virtual byte GetByte(int ordinal) { if (!CanGet) { @@ -84,7 +84,7 @@ public virtual byte GetByte(SmiEventSink sink, int ordinal) // valid for SqlDbTypes: Binary, VarBinary, Image, Udt, Xml, Char, VarChar, Text, NChar, NVarChar, NText // (Character type support needed for ExecuteXmlReader handling) - public virtual long GetBytesLength(SmiEventSink sink, int ordinal) + public virtual long GetBytesLength(int ordinal) { if (!CanGet) { @@ -95,7 +95,7 @@ public virtual long GetBytesLength(SmiEventSink sink, int ordinal) throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); } } - public virtual int GetBytes(SmiEventSink sink, int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) + public virtual int GetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) { if (!CanGet) { @@ -108,7 +108,7 @@ public virtual int GetBytes(SmiEventSink sink, int ordinal, long fieldOffset, by } // valid for character types: Char, VarChar, Text, NChar, NVarChar, NText - public virtual long GetCharsLength(SmiEventSink sink, int ordinal) + public virtual long GetCharsLength(int ordinal) { if (!CanGet) { @@ -119,7 +119,7 @@ public virtual long GetCharsLength(SmiEventSink sink, int ordinal) throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); } } - public virtual int GetChars(SmiEventSink sink, int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) + public virtual int GetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) { if (!CanGet) { @@ -130,7 +130,7 @@ public virtual int GetChars(SmiEventSink sink, int ordinal, long fieldOffset, ch throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); } } - public virtual string GetString(SmiEventSink sink, int ordinal) + public virtual string GetString(int ordinal) { if (!CanGet) { @@ -143,7 +143,7 @@ public virtual string GetString(SmiEventSink sink, int ordinal) } // valid for SqlDbType.SmallInt - public virtual short GetInt16(SmiEventSink sink, int ordinal) + public virtual short GetInt16(int ordinal) { if (!CanGet) { @@ -156,7 +156,7 @@ public virtual short GetInt16(SmiEventSink sink, int ordinal) } // valid for SqlDbType.Int - public virtual int GetInt32(SmiEventSink sink, int ordinal) + public virtual int GetInt32(int ordinal) { if (!CanGet) { @@ -169,7 +169,7 @@ public virtual int GetInt32(SmiEventSink sink, int ordinal) } // valid for SqlDbType.BigInt, SqlDbType.Money, SqlDbType.SmallMoney - public virtual long GetInt64(SmiEventSink sink, int ordinal) + public virtual long GetInt64(int ordinal) { if (!CanGet) { @@ -182,7 +182,7 @@ public virtual long GetInt64(SmiEventSink sink, int ordinal) } // valid for SqlDbType.Real - public virtual float GetSingle(SmiEventSink sink, int ordinal) + public virtual float GetSingle(int ordinal) { if (!CanGet) { @@ -195,7 +195,7 @@ public virtual float GetSingle(SmiEventSink sink, int ordinal) } // valid for SqlDbType.Float - public virtual double GetDouble(SmiEventSink sink, int ordinal) + public virtual double GetDouble(int ordinal) { if (!CanGet) { @@ -208,7 +208,7 @@ public virtual double GetDouble(SmiEventSink sink, int ordinal) } // valid for SqlDbType.Numeric (uses SqlDecimal since Decimal cannot hold full range) - public virtual SqlDecimal GetSqlDecimal(SmiEventSink sink, int ordinal) + public virtual SqlDecimal GetSqlDecimal(int ordinal) { if (!CanGet) { @@ -221,7 +221,7 @@ public virtual SqlDecimal GetSqlDecimal(SmiEventSink sink, int ordinal) } // valid for DateTime, SmallDateTime, Date, and DateTime2 - public virtual DateTime GetDateTime(SmiEventSink sink, int ordinal) + public virtual DateTime GetDateTime(int ordinal) { if (!CanGet) { @@ -234,7 +234,7 @@ public virtual DateTime GetDateTime(SmiEventSink sink, int ordinal) } // valid for UniqueIdentifier - public virtual Guid GetGuid(SmiEventSink sink, int ordinal) + public virtual Guid GetGuid(int ordinal) { if (!CanGet) { @@ -247,7 +247,7 @@ public virtual Guid GetGuid(SmiEventSink sink, int ordinal) } // valid for SqlDbType.Time - public virtual TimeSpan GetTimeSpan(SmiEventSink sink, int ordinal) + public virtual TimeSpan GetTimeSpan(int ordinal) { if (!CanGet) { @@ -260,7 +260,7 @@ public virtual TimeSpan GetTimeSpan(SmiEventSink sink, int ordinal) } // valid for DateTimeOffset - public virtual DateTimeOffset GetDateTimeOffset(SmiEventSink sink, int ordinal) + public virtual DateTimeOffset GetDateTimeOffset(int ordinal) { if (!CanGet) { @@ -274,31 +274,18 @@ public virtual DateTimeOffset GetDateTimeOffset(SmiEventSink sink, int ordinal) // valid for structured types // This method called for both get and set. - internal virtual SmiTypedGetterSetter GetTypedGetterSetter(SmiEventSink sink, int ordinal) + internal virtual SmiTypedGetterSetter GetTypedGetterSetter(int ordinal) { throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); } -#if NETFRAMEWORK - // valid for multi-valued types only - internal virtual bool NextElement(SmiEventSink sink) - { - if (!CanGet) - { - throw ADP.InternalError(ADP.InternalErrorCode.InvalidSmiCall); - } - else - { - throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); - } - } -#endif -#endregion + + #endregion -#region Setters + #region Setters // Set value to null // valid for all types - public virtual void SetDBNull(SmiEventSink sink, int ordinal) + public virtual void SetDBNull(int ordinal) { if (!CanSet) { @@ -311,7 +298,7 @@ public virtual void SetDBNull(SmiEventSink sink, int ordinal) } // valid for SqlDbType.Bit - public virtual void SetBoolean(SmiEventSink sink, int ordinal, bool value) + public virtual void SetBoolean(int ordinal, bool value) { if (!CanSet) { @@ -324,7 +311,7 @@ public virtual void SetBoolean(SmiEventSink sink, int ordinal, bool value) } // valid for SqlDbType.TinyInt - public virtual void SetByte(SmiEventSink sink, int ordinal, byte value) + public virtual void SetByte(int ordinal, byte value) { if (!CanSet) { @@ -340,7 +327,7 @@ public virtual void SetByte(SmiEventSink sink, int ordinal, byte value) // Use in combination with SetLength to ensure overwriting when necessary // valid for SqlDbTypes: Binary, VarBinary, Image, Udt, Xml // (VarBinary assumed for variants) - public virtual int SetBytes(SmiEventSink sink, int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) + public virtual int SetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) { if (!CanSet) { @@ -351,7 +338,7 @@ public virtual int SetBytes(SmiEventSink sink, int ordinal, long fieldOffset, by throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); } } - public virtual void SetBytesLength(SmiEventSink sink, int ordinal, long length) + public virtual void SetBytesLength(int ordinal, long length) { if (!CanSet) { @@ -367,7 +354,7 @@ public virtual void SetBytesLength(SmiEventSink sink, int ordinal, long length) // Use in combination with SetLength to ensure overwriting when necessary // valid for character types: Char, VarChar, Text, NChar, NVarChar, NText // (NVarChar and global clr collation assumed for variants) - public virtual int SetChars(SmiEventSink sink, int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) + public virtual int SetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) { if (!CanSet) { @@ -378,7 +365,7 @@ public virtual int SetChars(SmiEventSink sink, int ordinal, long fieldOffset, ch throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); } } - public virtual void SetCharsLength(SmiEventSink sink, int ordinal, long length) + public virtual void SetCharsLength(int ordinal, long length) { if (!CanSet) { @@ -391,7 +378,7 @@ public virtual void SetCharsLength(SmiEventSink sink, int ordinal, long length) } // valid for character types: Char, VarChar, Text, NChar, NVarChar, NText - public virtual void SetString(SmiEventSink sink, int ordinal, string value, int offset, int length) + public virtual void SetString(int ordinal, string value, int offset, int length) { if (!CanSet) { @@ -404,7 +391,7 @@ public virtual void SetString(SmiEventSink sink, int ordinal, string value, int } // valid for SqlDbType.SmallInt - public virtual void SetInt16(SmiEventSink sink, int ordinal, short value) + public virtual void SetInt16(int ordinal, short value) { if (!CanSet) { @@ -417,7 +404,7 @@ public virtual void SetInt16(SmiEventSink sink, int ordinal, short value) } // valid for SqlDbType.Int - public virtual void SetInt32(SmiEventSink sink, int ordinal, int value) + public virtual void SetInt32(int ordinal, int value) { if (!CanSet) { @@ -430,7 +417,7 @@ public virtual void SetInt32(SmiEventSink sink, int ordinal, int value) } // valid for SqlDbType.BigInt, SqlDbType.Money, SqlDbType.SmallMoney - public virtual void SetInt64(SmiEventSink sink, int ordinal, long value) + public virtual void SetInt64(int ordinal, long value) { if (!CanSet) { @@ -443,7 +430,7 @@ public virtual void SetInt64(SmiEventSink sink, int ordinal, long value) } // valid for SqlDbType.Real - public virtual void SetSingle(SmiEventSink sink, int ordinal, float value) + public virtual void SetSingle(int ordinal, float value) { if (!CanSet) { @@ -456,7 +443,7 @@ public virtual void SetSingle(SmiEventSink sink, int ordinal, float value) } // valid for SqlDbType.Float - public virtual void SetDouble(SmiEventSink sink, int ordinal, double value) + public virtual void SetDouble(int ordinal, double value) { if (!CanSet) { @@ -469,7 +456,7 @@ public virtual void SetDouble(SmiEventSink sink, int ordinal, double value) } // valid for SqlDbType.Numeric (uses SqlDecimal since Decimal cannot hold full range) - public virtual void SetSqlDecimal(SmiEventSink sink, int ordinal, SqlDecimal value) + public virtual void SetSqlDecimal(int ordinal, SqlDecimal value) { if (!CanSet) { @@ -482,7 +469,7 @@ public virtual void SetSqlDecimal(SmiEventSink sink, int ordinal, SqlDecimal val } // valid for DateTime, SmallDateTime, Date, and DateTime2 - public virtual void SetDateTime(SmiEventSink sink, int ordinal, DateTime value) + public virtual void SetDateTime(int ordinal, DateTime value) { if (!CanSet) { @@ -495,7 +482,7 @@ public virtual void SetDateTime(SmiEventSink sink, int ordinal, DateTime value) } // valid for UniqueIdentifier - public virtual void SetGuid(SmiEventSink sink, int ordinal, Guid value) + public virtual void SetGuid(int ordinal, Guid value) { if (!CanSet) { @@ -508,7 +495,7 @@ public virtual void SetGuid(SmiEventSink sink, int ordinal, Guid value) } // valid for SqlDbType.Time - public virtual void SetTimeSpan(SmiEventSink sink, int ordinal, TimeSpan value) + public virtual void SetTimeSpan(int ordinal, TimeSpan value) { if (!CanSet) { @@ -521,7 +508,7 @@ public virtual void SetTimeSpan(SmiEventSink sink, int ordinal, TimeSpan value) } // valid for DateTimeOffset - public virtual void SetDateTimeOffset(SmiEventSink sink, int ordinal, DateTimeOffset value) + public virtual void SetDateTimeOffset(int ordinal, DateTimeOffset value) { if (!CanSet) { @@ -533,7 +520,7 @@ public virtual void SetDateTimeOffset(SmiEventSink sink, int ordinal, DateTimeOf } } - public virtual void SetVariantMetaData(SmiEventSink sink, int ordinal, SmiMetaData metaData) + public virtual void SetVariantMetaData(int ordinal, SmiMetaData metaData) { // ******** OBSOLETING from SMI -- this should have been removed from ITypedSettersV3 // Intended to be removed prior to RTM. Sub-classes need not implement @@ -544,8 +531,8 @@ public virtual void SetVariantMetaData(SmiEventSink sink, int ordinal, SmiMetaDa throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); } - // valid for multi-valued types only - internal virtual void NewElement(SmiEventSink sink) + // valid for multivalued types only + internal virtual void NewElement() { if (!CanSet) { @@ -557,7 +544,7 @@ internal virtual void NewElement(SmiEventSink sink) } } - internal virtual void EndElements(SmiEventSink sink) + internal virtual void EndElements() { if (!CanSet) { @@ -568,7 +555,8 @@ internal virtual void EndElements(SmiEventSink sink) throw ADP.InternalError(ADP.InternalErrorCode.UnimplementedSMIMethod); } } -#endregion + + #endregion } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiXetterAccessMap.Common.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiXetterAccessMap.Common.cs deleted file mode 100644 index 8e0eb4baee..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiXetterAccessMap.Common.cs +++ /dev/null @@ -1,105 +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.Diagnostics; - -namespace Microsoft.Data.SqlClient.Server -{ - /// - /// Formal encoding of SMI's metadata-to-ITypedSetter/-from-ITypedGetter validity rules - /// - internal partial class SmiXetterAccessMap - { - // A couple of private constants to make the getter/setter access tables more readable - private const bool X = true; - private const bool _ = false; - - private static bool[,] s_isSetterAccessValid = { - // Setters as columns (abbreviated from XetterTypeCode names) - // SqlDbTypes as rows - // Current difference between setters and getters is that character setters do - // not need to support SetBytes - // bool, byte, bytes, chars, strng, int16, int32, int64, singl, doubl, sqldec, date, guid, varmd, Xetr, time, dtost - /*BigInt*/ - { _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*Binary*/ - { _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*Bit*/ - { X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*Char*/ - { _ , _ , _ , X , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*DTime*/ - { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , }, - /*Decimal*/ - { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , }, - /*Float*/ - { _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , }, - /*Image*/ - { _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*Int*/ - { _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*Money*/ - { _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*NChar*/ - { _ , _ , _ , X , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*NText*/ - { _ , _ , _ , X , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*NVarChar*/ - { _ , _ , _ , X , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*Real*/ - { _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*UniqueId*/ - { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , }, - /*SmDTime*/ - { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , }, - /*SmInt*/ - { _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*SmMoney*/ - { _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*Text*/ - { _ , _ , _ , X , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*Tstamp*/ - { _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*TinyInt*/ - { _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*VarBin*/ - { _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*VarChar*/ - { _ , _ , _ , X , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*Variant*/ - { X , X , X , X , X , X , X , X , X , X , X , X , X , X , _ , X , X , }, - /* 24 */ - { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*Xml*/ - { _ , _ , X , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /* 26 */ - { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /* 27 */ - { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /* 28 */ - { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*Udt*/ - { _ , _ , X , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , }, - /*Struct*/ - { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , }, - /*Date*/ - { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , }, - /*Time*/ - { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , }, - /*DTime2*/ - { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , _ , _ , _ , _ , _ , }, - /*DTOffset*/ - { _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , _ , X , }, - }; - - internal static bool IsSetterAccessValid(SmiMetaData metaData, SmiXetterTypeCode xetterType) - { - // Make sure no-one adds a new xetter type without updating this file! - Debug.Assert(SmiXetterTypeCode.XetBoolean <= xetterType && SmiXetterTypeCode.XetDateTimeOffset >= xetterType && - SmiXetterTypeCode.GetVariantMetaData != xetterType); - - return s_isSetterAccessValid[(int)metaData.SqlDbType, (int)xetterType]; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiXetterAccessMap.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiXetterAccessMap.cs new file mode 100644 index 0000000000..8e48c44735 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SmiXetterAccessMap.cs @@ -0,0 +1,115 @@ +// 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.Diagnostics; + +namespace Microsoft.Data.SqlClient.Server +{ + /// + /// Formal encoding of SMI's metadata-to-ITypedSetter/-from-ITypedGetter validity rules + /// + internal static class SmiXetterAccessMap + { + // A couple of private constants to make the getter/setter access tables more readable + private const bool X = true; + private const bool _ = false; + + #if NETFRAMEWORK + private static readonly bool[,] s_isGetterAccessValid = + { + // Getters as columns (abbreviated from XetterTypeCode names) + // SqlDbTypes as rows + // bool, byte, bytes, chars, strng, int16, int32, int64, singl, doubl, sqldec, date, guid, varmd, Xetr, time, dtost + /*BigInt*/ { _, _, _, _, _, _, _, X, _, _, _, _, _, _, _, _, _, }, + /*Binary*/ { _, _, X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Bit*/ { X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Char*/ { _, _, X, X, X, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*DTime*/ { _, _, _, _, _, _, _, _, _, _, _, X, _, _, _, _, _, }, + /*Decimal*/ { _, _, _, _, _, _, _, _, _, _, X, _, _, _, _, _, _, }, + /*Float*/ { _, _, _, _, _, _, _, _, _, X, _, _, _, _, _, _, _, }, + /*Image*/ { _, _, X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Int*/ { _, _, _, _, _, _, X, _, _, _, _, _, _, _, _, _, _, }, + /*Money*/ { _, _, _, _, _, _, _, X, _, _, _, _, _, _, _, _, _, }, + /*NChar*/ { _, _, X, X, X, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*NText*/ { _, _, X, X, X, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*NVarChar*/ { _, _, X, X, X, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Real*/ { _, _, _, _, _, _, _, _, X, _, _, _, _, _, _, _, _, }, + /*UniqueId*/ { _, _, _, _, _, _, _, _, _, _, _, _, X, _, _, _, _, }, + /*SmDTime*/ { _, _, _, _, _, _, _, _, _, _, _, X, _, _, _, _, _, }, + /*SmInt*/ { _, _, _, _, _, X, _, _, _, _, _, _, _, _, _, _, _, }, + /*SmMoney*/ { _, _, _, _, _, _, _, X, _, _, _, _, _, _, _, _, _, }, + /*Text*/ { _, _, X, X, X, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Tstamp*/ { _, _, X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*TinyInt*/ { _, X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*VarBin*/ { _, _, X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*VarChar*/ { _, _, X, X, X, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Variant*/ { X, X, X, X, X, X, X, X, X, X, X, X, X, X, _, X, X, }, + /* 24 */ { _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Xml*/ { _, _, X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /* 26 */ { _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /* 27 */ { _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /* 28 */ { _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Udt*/ { _, _, X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Struct*/ { _, _, X, _, _, _, _, _, _, _, _, _, _, _, X, _, _, }, + /*Date*/ { _, _, _, _, _, _, _, _, _, _, _, X, _, _, _, _, _, }, + /*Time*/ { _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, X, _, }, + /*DTime2*/ { _, _, _, _, _, _, _, _, _, _, _, X, _, _, _, _, _, }, + /*DTOffset*/ { _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, X, }, + }; + #endif + + private static readonly bool[,] s_isSetterAccessValid = + { + // Setters as columns (abbreviated from XetterTypeCode names) + // SqlDbTypes as rows + // Current difference between setters and getters is that character setters do + // not need to support SetBytes + // bool, byte, bytes, chars, strng, int16, int32, int64, singl, doubl, sqldec, date, guid, varmd, Xetr, time, dtost + /*BigInt*/ { _, _, _, _, _, _, _, X, _, _, _, _, _, _, _, _, _, }, + /*Binary*/ { _, _, X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Bit*/ { X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Char*/ { _, _, _, X, X, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*DTime*/ { _, _, _, _, _, _, _, _, _, _, _, X, _, _, _, _, _, }, + /*Decimal*/ { _, _, _, _, _, _, _, _, _, _, X, _, _, _, _, _, _, }, + /*Float*/ { _, _, _, _, _, _, _, _, _, X, _, _, _, _, _, _, _, }, + /*Image*/ { _, _, X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Int*/ { _, _, _, _, _, _, X, _, _, _, _, _, _, _, _, _, _, }, + /*Money*/ { _, _, _, _, _, _, _, X, _, _, _, _, _, _, _, _, _, }, + /*NChar*/ { _, _, _, X, X, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*NText*/ { _, _, _, X, X, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*NVarChar*/ { _, _, _, X, X, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Real*/ { _, _, _, _, _, _, _, _, X, _, _, _, _, _, _, _, _, }, + /*UniqueId*/ { _, _, _, _, _, _, _, _, _, _, _, _, X, _, _, _, _, }, + /*SmDTime*/ { _, _, _, _, _, _, _, _, _, _, _, X, _, _, _, _, _, }, + /*SmInt*/ { _, _, _, _, _, X, _, _, _, _, _, _, _, _, _, _, _, }, + /*SmMoney*/ { _, _, _, _, _, _, _, X, _, _, _, _, _, _, _, _, _, }, + /*Text*/ { _, _, _, X, X, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Tstamp*/ { _, _, X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*TinyInt*/ { _, X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*VarBin*/ { _, _, X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*VarChar*/ { _, _, _, X, X, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Variant*/ { X, X, X, X, X, X, X, X, X, X, X, X, X, X, _, X, X, }, + /* 24 */ { _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Xml*/ { _, _, X, _, X, _, _, _, _, _, _, _, _, _, _, _, _, }, + /* 26 */ { _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /* 27 */ { _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /* 28 */ { _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Udt*/ { _, _, X, _, _, _, _, _, _, _, _, _, _, _, _, _, _, }, + /*Struct*/ { _, _, _, _, _, _, _, _, _, _, _, _, _, _, X, _, _, }, + /*Date*/ { _, _, _, _, _, _, _, _, _, _, _, X, _, _, _, _, _, }, + /*Time*/ { _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, X, _, }, + /*DTime2*/ { _, _, _, _, _, _, _, _, _, _, _, X, _, _, _, _, _, }, + /*DTOffset*/ { _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, X, }, + }; + + internal static bool IsSetterAccessValid(SmiMetaData metaData, SmiXetterTypeCode xetterType) + { + // Make sure no-one adds a new xetter type without updating this file! + Debug.Assert(SmiXetterTypeCode.XetBoolean <= xetterType && SmiXetterTypeCode.XetDateTimeOffset >= xetterType && + SmiXetterTypeCode.GetVariantMetaData != xetterType); + + return s_isSetterAccessValid[(int)metaData.SqlDbType, (int)xetterType]; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlDataRecord.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlDataRecord.cs index 1a0f92fdbe..4bb302cd7b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlDataRecord.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlDataRecord.cs @@ -16,7 +16,6 @@ public partial class SqlDataRecord : IDataRecord { private readonly SmiRecordBuffer _recordBuffer; private readonly SmiExtendedMetaData[] _columnSmiMetaData; - private readonly SmiEventSink_Default _eventSink; private readonly SqlMetaData[] _columnMetaData; private FieldNameLookup _fieldNameLookup; private readonly bool _usesStringStorageForXml = false; @@ -101,37 +100,37 @@ public virtual int GetOrdinal(string name) public virtual object this[string name] => GetValue(GetOrdinal(name)); /// - public virtual bool GetBoolean(int ordinal) => ValueUtilsSmi.GetBoolean(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual bool GetBoolean(int ordinal) => ValueUtilsSmi.GetBoolean(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual byte GetByte(int ordinal) => ValueUtilsSmi.GetByte(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual byte GetByte(int ordinal) => ValueUtilsSmi.GetByte(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual long GetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) => ValueUtilsSmi.GetBytes(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), fieldOffset, buffer, bufferOffset, length, throwOnNull: true); + public virtual long GetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) => ValueUtilsSmi.GetBytes(_recordBuffer, ordinal, GetSmiMetaData(ordinal), fieldOffset, buffer, bufferOffset, length, throwOnNull: true); /// public virtual char GetChar(int ordinal) => throw ADP.NotSupported(); /// - public virtual long GetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) => ValueUtilsSmi.GetChars(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), fieldOffset, buffer, bufferOffset, length); + public virtual long GetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) => ValueUtilsSmi.GetChars(_recordBuffer, ordinal, GetSmiMetaData(ordinal), fieldOffset, buffer, bufferOffset, length); /// - public virtual Guid GetGuid(int ordinal) => ValueUtilsSmi.GetGuid(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual Guid GetGuid(int ordinal) => ValueUtilsSmi.GetGuid(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual short GetInt16(int ordinal) => ValueUtilsSmi.GetInt16(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual short GetInt16(int ordinal) => ValueUtilsSmi.GetInt16(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual int GetInt32(int ordinal) => ValueUtilsSmi.GetInt32(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual int GetInt32(int ordinal) => ValueUtilsSmi.GetInt32(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual long GetInt64(int ordinal) => ValueUtilsSmi.GetInt64(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual long GetInt64(int ordinal) => ValueUtilsSmi.GetInt64(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual float GetFloat(int ordinal) => ValueUtilsSmi.GetSingle(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual float GetFloat(int ordinal) => ValueUtilsSmi.GetSingle(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual double GetDouble(int ordinal) => ValueUtilsSmi.GetDouble(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual double GetDouble(int ordinal) => ValueUtilsSmi.GetDouble(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// public virtual string GetString(int ordinal) @@ -139,31 +138,31 @@ public virtual string GetString(int ordinal) SmiMetaData colMeta = GetSmiMetaData(ordinal); if (_usesStringStorageForXml && colMeta.SqlDbType == SqlDbType.Xml) { - return ValueUtilsSmi.GetString(_eventSink, _recordBuffer, ordinal, s_maxNVarCharForXml); + return ValueUtilsSmi.GetString(_recordBuffer, ordinal, s_maxNVarCharForXml); } else { - return ValueUtilsSmi.GetString(_eventSink, _recordBuffer, ordinal, colMeta); + return ValueUtilsSmi.GetString(_recordBuffer, ordinal, colMeta); } } /// - public virtual decimal GetDecimal(int ordinal) => ValueUtilsSmi.GetDecimal(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual decimal GetDecimal(int ordinal) => ValueUtilsSmi.GetDecimal(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual DateTime GetDateTime(int ordinal) => ValueUtilsSmi.GetDateTime(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual DateTime GetDateTime(int ordinal) => ValueUtilsSmi.GetDateTime(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual DateTimeOffset GetDateTimeOffset(int ordinal) => ValueUtilsSmi.GetDateTimeOffset(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual DateTimeOffset GetDateTimeOffset(int ordinal) => ValueUtilsSmi.GetDateTimeOffset(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual TimeSpan GetTimeSpan(int ordinal) => ValueUtilsSmi.GetTimeSpan(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual TimeSpan GetTimeSpan(int ordinal) => ValueUtilsSmi.GetTimeSpan(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// public virtual bool IsDBNull(int ordinal) { ThrowIfInvalidOrdinal(ordinal); - return ValueUtilsSmi.IsDBNull(_eventSink, _recordBuffer, ordinal); + return ValueUtilsSmi.IsDBNull(_recordBuffer, ordinal); } /// @@ -198,7 +197,7 @@ public virtual int GetSqlValues(object[] values) } /// - public virtual SqlBinary GetSqlBinary(int ordinal) => ValueUtilsSmi.GetSqlBinary(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual SqlBinary GetSqlBinary(int ordinal) => ValueUtilsSmi.GetSqlBinary(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// public virtual SqlBytes GetSqlBytes(int ordinal) => GetSqlBytesFrameworkSpecific(ordinal); @@ -207,43 +206,43 @@ public virtual int GetSqlValues(object[] values) public virtual SqlXml GetSqlXml(int ordinal) => GetSqlXmlFrameworkSpecific(ordinal); /// - public virtual SqlBoolean GetSqlBoolean(int ordinal) => ValueUtilsSmi.GetSqlBoolean(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual SqlBoolean GetSqlBoolean(int ordinal) => ValueUtilsSmi.GetSqlBoolean(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual SqlByte GetSqlByte(int ordinal) => ValueUtilsSmi.GetSqlByte(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual SqlByte GetSqlByte(int ordinal) => ValueUtilsSmi.GetSqlByte(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// public virtual SqlChars GetSqlChars(int ordinal) => GetSqlCharsFrameworkSpecific(ordinal); /// - public virtual SqlInt16 GetSqlInt16(int ordinal) => ValueUtilsSmi.GetSqlInt16(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual SqlInt16 GetSqlInt16(int ordinal) => ValueUtilsSmi.GetSqlInt16(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual SqlInt32 GetSqlInt32(int ordinal) => ValueUtilsSmi.GetSqlInt32(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual SqlInt32 GetSqlInt32(int ordinal) => ValueUtilsSmi.GetSqlInt32(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual SqlInt64 GetSqlInt64(int ordinal) => ValueUtilsSmi.GetSqlInt64(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual SqlInt64 GetSqlInt64(int ordinal) => ValueUtilsSmi.GetSqlInt64(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual SqlSingle GetSqlSingle(int ordinal) => ValueUtilsSmi.GetSqlSingle(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual SqlSingle GetSqlSingle(int ordinal) => ValueUtilsSmi.GetSqlSingle(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual SqlDouble GetSqlDouble(int ordinal) => ValueUtilsSmi.GetSqlDouble(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual SqlDouble GetSqlDouble(int ordinal) => ValueUtilsSmi.GetSqlDouble(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual SqlMoney GetSqlMoney(int ordinal) => ValueUtilsSmi.GetSqlMoney(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual SqlMoney GetSqlMoney(int ordinal) => ValueUtilsSmi.GetSqlMoney(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual SqlDateTime GetSqlDateTime(int ordinal) => ValueUtilsSmi.GetSqlDateTime(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual SqlDateTime GetSqlDateTime(int ordinal) => ValueUtilsSmi.GetSqlDateTime(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual SqlDecimal GetSqlDecimal(int ordinal) => ValueUtilsSmi.GetSqlDecimal(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual SqlDecimal GetSqlDecimal(int ordinal) => ValueUtilsSmi.GetSqlDecimal(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual SqlString GetSqlString(int ordinal) => ValueUtilsSmi.GetSqlString(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual SqlString GetSqlString(int ordinal) => ValueUtilsSmi.GetSqlString(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// - public virtual SqlGuid GetSqlGuid(int ordinal) => ValueUtilsSmi.GetSqlGuid(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal)); + public virtual SqlGuid GetSqlGuid(int ordinal) => ValueUtilsSmi.GetSqlGuid(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); /// // ISqlUpdateableRecord Implementation @@ -253,42 +252,42 @@ public virtual int GetSqlValues(object[] values) public virtual void SetValue(int ordinal, object value) => SetValueFrameworkSpecific(ordinal, value); /// - public virtual void SetBoolean(int ordinal, bool value) => ValueUtilsSmi.SetBoolean(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetBoolean(int ordinal, bool value) => ValueUtilsSmi.SetBoolean(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetByte(int ordinal, byte value) => ValueUtilsSmi.SetByte(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetByte(int ordinal, byte value) => ValueUtilsSmi.SetByte(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) => ValueUtilsSmi.SetBytes(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), fieldOffset, buffer, bufferOffset, length); + public virtual void SetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) => ValueUtilsSmi.SetBytes(_recordBuffer, ordinal, GetSmiMetaData(ordinal), fieldOffset, buffer, bufferOffset, length); /// public virtual void SetChar(int ordinal, char value) => throw ADP.NotSupported(); /// - public virtual void SetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) => ValueUtilsSmi.SetChars(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), fieldOffset, buffer, bufferOffset, length); + public virtual void SetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) => ValueUtilsSmi.SetChars(_recordBuffer, ordinal, GetSmiMetaData(ordinal), fieldOffset, buffer, bufferOffset, length); /// - public virtual void SetInt16(int ordinal, short value) => ValueUtilsSmi.SetInt16(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetInt16(int ordinal, short value) => ValueUtilsSmi.SetInt16(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetInt32(int ordinal, int value) => ValueUtilsSmi.SetInt32(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetInt32(int ordinal, int value) => ValueUtilsSmi.SetInt32(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetInt64(int ordinal, long value) => ValueUtilsSmi.SetInt64(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetInt64(int ordinal, long value) => ValueUtilsSmi.SetInt64(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetFloat(int ordinal, float value) => ValueUtilsSmi.SetSingle(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetFloat(int ordinal, float value) => ValueUtilsSmi.SetSingle(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetDouble(int ordinal, double value) => ValueUtilsSmi.SetDouble(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetDouble(int ordinal, double value) => ValueUtilsSmi.SetDouble(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetString(int ordinal, string value) => ValueUtilsSmi.SetString(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetString(int ordinal, string value) => ValueUtilsSmi.SetString(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetDecimal(int ordinal, decimal value) => ValueUtilsSmi.SetDecimal(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetDecimal(int ordinal, decimal value) => ValueUtilsSmi.SetDecimal(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetDateTime(int ordinal, DateTime value) => ValueUtilsSmi.SetDateTime(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetDateTime(int ordinal, DateTime value) => ValueUtilsSmi.SetDateTime(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// public virtual void SetTimeSpan(int ordinal, TimeSpan value) => SetTimeSpanFrameworkSpecific(ordinal, value); @@ -300,59 +299,59 @@ public virtual int GetSqlValues(object[] values) public virtual void SetDBNull(int ordinal) { ThrowIfInvalidOrdinal(ordinal); - ValueUtilsSmi.SetDBNull(_eventSink, _recordBuffer, ordinal); + ValueUtilsSmi.SetDBNull(_recordBuffer, ordinal); } /// - public virtual void SetGuid(int ordinal, Guid value) => ValueUtilsSmi.SetGuid(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetGuid(int ordinal, Guid value) => ValueUtilsSmi.SetGuid(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetSqlBoolean(int ordinal, SqlBoolean value) => ValueUtilsSmi.SetSqlBoolean(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetSqlBoolean(int ordinal, SqlBoolean value) => ValueUtilsSmi.SetSqlBoolean(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetSqlByte(int ordinal, SqlByte value) => ValueUtilsSmi.SetSqlByte(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetSqlByte(int ordinal, SqlByte value) => ValueUtilsSmi.SetSqlByte(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetSqlInt16(int ordinal, SqlInt16 value) => ValueUtilsSmi.SetSqlInt16(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetSqlInt16(int ordinal, SqlInt16 value) => ValueUtilsSmi.SetSqlInt16(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetSqlInt32(int ordinal, SqlInt32 value) => ValueUtilsSmi.SetSqlInt32(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetSqlInt32(int ordinal, SqlInt32 value) => ValueUtilsSmi.SetSqlInt32(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetSqlInt64(int ordinal, SqlInt64 value) => ValueUtilsSmi.SetSqlInt64(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetSqlInt64(int ordinal, SqlInt64 value) => ValueUtilsSmi.SetSqlInt64(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetSqlSingle(int ordinal, SqlSingle value) => ValueUtilsSmi.SetSqlSingle(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetSqlSingle(int ordinal, SqlSingle value) => ValueUtilsSmi.SetSqlSingle(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetSqlDouble(int ordinal, SqlDouble value) => ValueUtilsSmi.SetSqlDouble(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetSqlDouble(int ordinal, SqlDouble value) => ValueUtilsSmi.SetSqlDouble(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetSqlMoney(int ordinal, SqlMoney value) => ValueUtilsSmi.SetSqlMoney(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetSqlMoney(int ordinal, SqlMoney value) => ValueUtilsSmi.SetSqlMoney(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetSqlDateTime(int ordinal, SqlDateTime value) => ValueUtilsSmi.SetSqlDateTime(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetSqlDateTime(int ordinal, SqlDateTime value) => ValueUtilsSmi.SetSqlDateTime(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetSqlXml(int ordinal, SqlXml value) => ValueUtilsSmi.SetSqlXml(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetSqlXml(int ordinal, SqlXml value) => ValueUtilsSmi.SetSqlXml(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetSqlDecimal(int ordinal, SqlDecimal value) => ValueUtilsSmi.SetSqlDecimal(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetSqlDecimal(int ordinal, SqlDecimal value) => ValueUtilsSmi.SetSqlDecimal(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetSqlString(int ordinal, SqlString value) => ValueUtilsSmi.SetSqlString(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetSqlString(int ordinal, SqlString value) => ValueUtilsSmi.SetSqlString(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetSqlBinary(int ordinal, SqlBinary value) => ValueUtilsSmi.SetSqlBinary(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetSqlBinary(int ordinal, SqlBinary value) => ValueUtilsSmi.SetSqlBinary(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetSqlGuid(int ordinal, SqlGuid value) => ValueUtilsSmi.SetSqlGuid(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetSqlGuid(int ordinal, SqlGuid value) => ValueUtilsSmi.SetSqlGuid(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetSqlChars(int ordinal, SqlChars value) => ValueUtilsSmi.SetSqlChars(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetSqlChars(int ordinal, SqlChars value) => ValueUtilsSmi.SetSqlChars(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); /// - public virtual void SetSqlBytes(int ordinal, SqlBytes value) => ValueUtilsSmi.SetSqlBytes(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + public virtual void SetSqlBytes(int ordinal, SqlBytes value) => ValueUtilsSmi.SetSqlBytes(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); // SqlDataRecord public API /// @@ -366,9 +365,7 @@ public SqlDataRecord(params SqlMetaData[] metaData) _columnMetaData = new SqlMetaData[metaData.Length]; _columnSmiMetaData = new SmiExtendedMetaData[metaData.Length]; -#if NETFRAMEWORK - ulong smiVersion = SmiVersion; -#endif + for (int i = 0; i < _columnSmiMetaData.Length; i++) { if (metaData[i] == null) @@ -377,32 +374,13 @@ public SqlDataRecord(params SqlMetaData[] metaData) } _columnMetaData[i] = metaData[i]; _columnSmiMetaData[i] = MetaDataUtilsSmi.SqlMetaDataToSmiExtendedMetaData(_columnMetaData[i]); -#if NETFRAMEWORK - if (!MetaDataUtilsSmi.IsValidForSmiVersion(_columnSmiMetaData[i], smiVersion)) - { - throw ADP.VersionDoesNotSupportDataType(_columnSmiMetaData[i].TypeName); - } -#endif } - _eventSink = new SmiEventSink_Default(); -#if NETFRAMEWORK - if (InOutOfProcHelper.InProc) - { - _recordContext = SmiContextFactory.Instance.GetCurrentContext(); - _recordBuffer = _recordContext.CreateRecordBuffer(_columnSmiMetaData, _eventSink); - _usesStringStorageForXml = false; - } - else - { - _recordContext = null; - _recordBuffer = new MemoryRecordBuffer(_columnSmiMetaData); - _usesStringStorageForXml = true; - } -#else _recordBuffer = new MemoryRecordBuffer(_columnSmiMetaData); - _eventSink.ProcessMessagesAndThrow(); -#endif + + #if NETFRAMEWORK + _usesStringStorageForXml = true; + #endif } internal SmiExtendedMetaData GetSmiMetaData(int ordinal) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlDataRecord.netcore.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlDataRecord.netcore.cs index 2a24606e77..6449aa6c62 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlDataRecord.netcore.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlDataRecord.netcore.cs @@ -16,22 +16,22 @@ public partial class SqlDataRecord : IDataRecord #if NET [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] #endif - private Type GetFieldTypeFrameworkSpecific(int ordinal) - => MetaType.GetMetaTypeFromSqlDbType(GetSqlMetaData(ordinal).SqlDbType, false).ClassType; + private Type GetFieldTypeFrameworkSpecific(int ordinal) => + MetaType.GetMetaTypeFromSqlDbType(GetSqlMetaData(ordinal).SqlDbType, false).ClassType; - private object GetValueFrameworkSpecific(int ordinal) - => ValueUtilsSmi.GetValue200(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), null); - private object GetSqlValueFrameworkSpecific(int ordinal) - => ValueUtilsSmi.GetSqlValue200(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), null); + private object GetValueFrameworkSpecific(int ordinal) => + ValueUtilsSmi.GetValue200(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); + private object GetSqlValueFrameworkSpecific(int ordinal) => + ValueUtilsSmi.GetSqlValue200(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); - private SqlBytes GetSqlBytesFrameworkSpecific(int ordinal) - => ValueUtilsSmi.GetSqlBytes(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), null); + private SqlBytes GetSqlBytesFrameworkSpecific(int ordinal) => + ValueUtilsSmi.GetSqlBytes(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); - private SqlXml GetSqlXmlFrameworkSpecific(int ordinal) - => ValueUtilsSmi.GetSqlXml(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), null); + private SqlXml GetSqlXmlFrameworkSpecific(int ordinal) => + ValueUtilsSmi.GetSqlXml(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); - private SqlChars GetSqlCharsFrameworkSpecific(int ordinal) - => ValueUtilsSmi.GetSqlChars(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), null); + private SqlChars GetSqlCharsFrameworkSpecific(int ordinal) => + ValueUtilsSmi.GetSqlChars(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); private int SetValuesFrameworkSpecific(params object[] values) { if (values == null) @@ -64,7 +64,7 @@ private int SetValuesFrameworkSpecific(params object[] values) // the validation loop and here, or if an invalid UDT was sent). for (int i = 0; i < copyLength; i++) { - ValueUtilsSmi.SetCompatibleValueV200(_eventSink, _recordBuffer, i, GetSmiMetaData(i), values[i], typeCodes[i], offset: 0, peekAhead: null); + ValueUtilsSmi.SetCompatibleValueV200(_recordBuffer, i, GetSmiMetaData(i), values[i], typeCodes[i], offset: 0, peekAhead: null); } return copyLength; @@ -84,14 +84,14 @@ private void SetValueFrameworkSpecific(int ordinal, object value) throw ADP.InvalidCast(); } - ValueUtilsSmi.SetCompatibleValueV200(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value, typeCode, offset: 0, peekAhead: null); + ValueUtilsSmi.SetCompatibleValueV200(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value, typeCode, offset: 0, peekAhead: null); } private void SetTimeSpanFrameworkSpecific(int ordinal, TimeSpan value) - => ValueUtilsSmi.SetTimeSpan(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + => ValueUtilsSmi.SetTimeSpan(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); private void SetDateTimeOffsetFrameworkSpecific(int ordinal, DateTimeOffset value) - => ValueUtilsSmi.SetDateTimeOffset(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value); + => ValueUtilsSmi.SetDateTimeOffset(_recordBuffer, ordinal, GetSmiMetaData(ordinal), value); } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlDataRecord.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlDataRecord.netfx.cs index 45d4be4d6b..7f67e951da 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlDataRecord.netfx.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlDataRecord.netfx.cs @@ -12,8 +12,6 @@ namespace Microsoft.Data.SqlClient.Server /// public partial class SqlDataRecord : IDataRecord { - private readonly SmiContext _recordContext; - private Type GetFieldTypeFrameworkSpecific(int ordinal) { SqlMetaData md = GetSqlMetaData(ordinal); @@ -30,31 +28,23 @@ private Type GetFieldTypeFrameworkSpecific(int ordinal) private object GetValueFrameworkSpecific(int ordinal) { SmiMetaData metaData = GetSmiMetaData(ordinal); - if (SmiVersion >= SmiContextFactory.Sql2008Version) - { - return ValueUtilsSmi.GetValue200(_eventSink, _recordBuffer, ordinal, metaData, _recordContext); - } - else - { - return ValueUtilsSmi.GetValue(_eventSink, _recordBuffer, ordinal, metaData, _recordContext); - } + return ValueUtilsSmi.GetValue200(_recordBuffer, ordinal, metaData); } private object GetSqlValueFrameworkSpecific(int ordinal) { SmiMetaData metaData = GetSmiMetaData(ordinal); - if (SmiVersion >= SmiContextFactory.Sql2008Version) - { - return ValueUtilsSmi.GetSqlValue200(_eventSink, _recordBuffer, ordinal, metaData, _recordContext); - } - return ValueUtilsSmi.GetSqlValue(_eventSink, _recordBuffer, ordinal, metaData, _recordContext); + return ValueUtilsSmi.GetSqlValue200(_recordBuffer, ordinal, metaData); } - private SqlBytes GetSqlBytesFrameworkSpecific(int ordinal) => ValueUtilsSmi.GetSqlBytes(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), _recordContext); + private SqlBytes GetSqlBytesFrameworkSpecific(int ordinal) => + ValueUtilsSmi.GetSqlBytes(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); - private SqlXml GetSqlXmlFrameworkSpecific(int ordinal) => ValueUtilsSmi.GetSqlXml(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), _recordContext); + private SqlXml GetSqlXmlFrameworkSpecific(int ordinal) => + ValueUtilsSmi.GetSqlXml(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); - private SqlChars GetSqlCharsFrameworkSpecific(int ordinal) => ValueUtilsSmi.GetSqlChars(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), _recordContext); + private SqlChars GetSqlCharsFrameworkSpecific(int ordinal) => + ValueUtilsSmi.GetSqlChars(_recordBuffer, ordinal, GetSmiMetaData(ordinal)); private int SetValuesFrameworkSpecific(params object[] values) { @@ -76,9 +66,7 @@ private int SetValuesFrameworkSpecific(params object[] values) metaData.SqlDbType, isMultiValued: false, values[i], - metaData.Type, - SmiVersion - ); + metaData.Type); if (typeCodes[i] == ExtendedClrTypeCode.Invalid) { throw ADP.InvalidCast(); @@ -89,14 +77,14 @@ private int SetValuesFrameworkSpecific(params object[] values) // the validation loop and here, or if an invalid UDT was sent). for (int i = 0; i < copyLength; i++) { - if (SmiVersion >= SmiContextFactory.Sql2008Version) - { - ValueUtilsSmi.SetCompatibleValueV200(_eventSink, _recordBuffer, i, GetSmiMetaData(i), values[i], typeCodes[i], offset: 0, peekAhead: null); - } - else - { - ValueUtilsSmi.SetCompatibleValue(_eventSink, _recordBuffer, i, GetSmiMetaData(i), values[i], typeCodes[i], offset: 0); - } + ValueUtilsSmi.SetCompatibleValueV200( + _recordBuffer, + ordinal: i, + GetSmiMetaData(i), + values[i], + typeCodes[i], + offset: 0, + peekAhead: null); } return copyLength; @@ -109,27 +97,36 @@ private void SetValueFrameworkSpecific(int ordinal, object value) metaData.SqlDbType, isMultiValued: false, value, - metaData.Type, - SmiVersion - ); + metaData.Type); if (typeCode == ExtendedClrTypeCode.Invalid) { throw ADP.InvalidCast(); } - - if (SmiVersion >= SmiContextFactory.Sql2008Version) - { - ValueUtilsSmi.SetCompatibleValueV200(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value, typeCode, offset: 0, peekAhead: null); - } - else - { - ValueUtilsSmi.SetCompatibleValue(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value, typeCode, offset: 0); - } + + ValueUtilsSmi.SetCompatibleValueV200( + _recordBuffer, + ordinal, + GetSmiMetaData(ordinal), + value, + typeCode, + offset: 0, + peekAhead: null); } - private void SetTimeSpanFrameworkSpecific(int ordinal, TimeSpan value) => ValueUtilsSmi.SetTimeSpan(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value, SmiVersion >= SmiContextFactory.Sql2008Version); - private void SetDateTimeOffsetFrameworkSpecific(int ordinal, DateTimeOffset value) => ValueUtilsSmi.SetDateTimeOffset(_eventSink, _recordBuffer, ordinal, GetSmiMetaData(ordinal), value, SmiVersion >= SmiContextFactory.Sql2008Version); - - private ulong SmiVersion => InOutOfProcHelper.InProc ? SmiContextFactory.Instance.NegotiatedSmiVersion : SmiContextFactory.LatestVersion; + private void SetTimeSpanFrameworkSpecific(int ordinal, TimeSpan value) => + ValueUtilsSmi.SetTimeSpan( + _recordBuffer, + ordinal, + GetSmiMetaData(ordinal), + value, + settersSupport2008DateTime: true); + + private void SetDateTimeOffsetFrameworkSpecific(int ordinal, DateTimeOffset value) => + ValueUtilsSmi.SetDateTimeOffset( + _recordBuffer, + ordinal, + GetSmiMetaData(ordinal), + value, + settersSupport2008DateTime: true); } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlMetaData.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlMetaData.cs index 84613721d4..2c3c6acaab 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlMetaData.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlMetaData.cs @@ -18,7 +18,7 @@ namespace Microsoft.Data.SqlClient.Server // 1) enforcing immutability. // 2) Inferring type from a value. // 3) Adjusting a value to match the metadata. - public sealed partial class SqlMetaData + public sealed class SqlMetaData { private const long MaxUnicodeLength = 4000; private const long MaxANSILength = 8000; @@ -97,80 +97,44 @@ public sealed partial class SqlMetaData // Array of default-valued metadata ordered by corresponding SqlDbType. - internal static SqlMetaData[] s_defaults = - { - // new SqlMetaData(name, DbType, SqlDbType, MaxLen, Prec, Scale, Locale, DatabaseName, SchemaName, isPartialLength) - new SqlMetaData("bigint", SqlDbType.BigInt, - 8, 19, 0, 0, SqlCompareOptions.None, false), // SqlDbType.BigInt - new SqlMetaData("binary", SqlDbType.Binary, - 1, 0, 0, 0, SqlCompareOptions.None, false), // SqlDbType.Binary - new SqlMetaData("bit", SqlDbType.Bit, - 1, 1, 0, 0, SqlCompareOptions.None, false), // SqlDbType.Bit - new SqlMetaData("char", SqlDbType.Char, - 1, 0, 0, 0, DefaultStringCompareOptions, false), // SqlDbType.Char - new SqlMetaData("datetime", SqlDbType.DateTime, - 8, 23, 3, 0, SqlCompareOptions.None, false), // SqlDbType.DateTime - new SqlMetaData("decimal", SqlDbType.Decimal, - 9, 18, 0, 0, SqlCompareOptions.None, false), // SqlDbType.Decimal - new SqlMetaData("float", SqlDbType.Float, - 8, 53, 0, 0, SqlCompareOptions.None, false), // SqlDbType.Float - new SqlMetaData("image", SqlDbType.Image, - UnlimitedMaxLength, 0, 0, 0, SqlCompareOptions.None, false), // SqlDbType.Image - new SqlMetaData("int", SqlDbType.Int, - 4, 10, 0, 0, SqlCompareOptions.None, false), // SqlDbType.Int - new SqlMetaData("money", SqlDbType.Money, - 8, 19, 4, 0, SqlCompareOptions.None, false), // SqlDbType.Money - new SqlMetaData("nchar", SqlDbType.NChar, - 1, 0, 0, 0, DefaultStringCompareOptions, false), // SqlDbType.NChar - new SqlMetaData("ntext", SqlDbType.NText, - UnlimitedMaxLength, 0, 0, 0, DefaultStringCompareOptions, false), // SqlDbType.NText - new SqlMetaData("nvarchar", SqlDbType.NVarChar, - MaxUnicodeLength, 0, 0, 0, DefaultStringCompareOptions, false), // SqlDbType.NVarChar - new SqlMetaData("real", SqlDbType.Real, - 4, 24, 0, 0, SqlCompareOptions.None, false), // SqlDbType.Real - new SqlMetaData("uniqueidentifier", SqlDbType.UniqueIdentifier, - 16, 0, 0, 0, SqlCompareOptions.None, false), // SqlDbType.UniqueIdentifier - new SqlMetaData("smalldatetime", SqlDbType.SmallDateTime, - 4, 16, 0, 0, SqlCompareOptions.None, false), // SqlDbType.SmallDateTime - new SqlMetaData("smallint", SqlDbType.SmallInt, - 2, 5, 0, 0, SqlCompareOptions.None, false), // SqlDbType.SmallInt - new SqlMetaData("smallmoney", SqlDbType.SmallMoney, - 4, 10, 4, 0, SqlCompareOptions.None, false), // SqlDbType.SmallMoney - new SqlMetaData("text", SqlDbType.Text, - UnlimitedMaxLength, 0, 0, 0, DefaultStringCompareOptions, false), // SqlDbType.Text - new SqlMetaData("timestamp", SqlDbType.Timestamp, - 8, 0, 0, 0, SqlCompareOptions.None, false), // SqlDbType.Timestamp - new SqlMetaData("tinyint", SqlDbType.TinyInt, - 1, 3, 0, 0, SqlCompareOptions.None, false), // SqlDbType.TinyInt - new SqlMetaData("varbinary", SqlDbType.VarBinary, - MaxBinaryLength, 0, 0, 0, SqlCompareOptions.None, false), // SqlDbType.VarBinary - new SqlMetaData("varchar", SqlDbType.VarChar, - MaxANSILength, 0, 0, 0, DefaultStringCompareOptions, false), // SqlDbType.VarChar - new SqlMetaData("sql_variant", SqlDbType.Variant, - 8016, 0, 0, 0, SqlCompareOptions.None, false), // SqlDbType.Variant - new SqlMetaData("nvarchar", SqlDbType.NVarChar, - 1, 0, 0, 0, DefaultStringCompareOptions, false), // Placeholder for value 24 - new SqlMetaData("xml", SqlDbType.Xml, - UnlimitedMaxLength, 0, 0, 0, DefaultStringCompareOptions, true), // SqlDbType.Xml - new SqlMetaData("nvarchar", SqlDbType.NVarChar, - 1, 0, 0, 0, DefaultStringCompareOptions, false), // Placeholder for value 26 - new SqlMetaData("nvarchar", SqlDbType.NVarChar, - MaxUnicodeLength, 0, 0, 0, DefaultStringCompareOptions, false), // Placeholder for value 27 - new SqlMetaData("nvarchar", SqlDbType.NVarChar, - MaxUnicodeLength, 0, 0, 0, DefaultStringCompareOptions, false), // Placeholder for value 28 - new SqlMetaData("udt", SqlDbType.Udt, - 0, 0, 0, 0, SqlCompareOptions.None, false), // SqlDbType.Udt = 29 - new SqlMetaData("table", SqlDbType.Structured, - 0, 0, 0, 0, SqlCompareOptions.None, false), // SqlDbType.Structured - new SqlMetaData("date", SqlDbType.Date, - 3, 10,0, 0, SqlCompareOptions.None, false), // SqlDbType.Date - new SqlMetaData("time", SqlDbType.Time, - 5, 0, 7, 0, SqlCompareOptions.None, false), // SqlDbType.Time - new SqlMetaData("datetime2", SqlDbType.DateTime2, - 8, 0, 7, 0, SqlCompareOptions.None, false), // SqlDbType.DateTime2 - new SqlMetaData("datetimeoffset", SqlDbType.DateTimeOffset, - 10, 0, 7, 0, SqlCompareOptions.None, false), // SqlDbType.DateTimeOffset - }; + private static readonly SqlMetaData[] s_defaults = + { + new SqlMetaData("bigint", SqlDbType.BigInt, 8, 19, 0, 0, SqlCompareOptions.None), + new SqlMetaData("binary", SqlDbType.Binary, 1, 0, 0, 0, SqlCompareOptions.None), + new SqlMetaData("bit", SqlDbType.Bit, 1, 1, 0, 0, SqlCompareOptions.None), + new SqlMetaData("char", SqlDbType.Char, 1, 0, 0, 0, DefaultStringCompareOptions), + new SqlMetaData("datetime", SqlDbType.DateTime, 8, 23, 3, 0, SqlCompareOptions.None), + new SqlMetaData("decimal", SqlDbType.Decimal, 9, 18, 0, 0, SqlCompareOptions.None), + new SqlMetaData("float", SqlDbType.Float, 8, 53, 0, 0, SqlCompareOptions.None), + new SqlMetaData("image", SqlDbType.Image, UnlimitedMaxLength, 0, 0, 0, SqlCompareOptions.None), + new SqlMetaData("int", SqlDbType.Int, 4, 10, 0, 0, SqlCompareOptions.None), + new SqlMetaData("money", SqlDbType.Money, 8, 19, 4, 0, SqlCompareOptions.None), + new SqlMetaData("nchar", SqlDbType.NChar, 1, 0, 0, 0, DefaultStringCompareOptions), + new SqlMetaData("ntext", SqlDbType.NText, UnlimitedMaxLength, 0, 0, 0, DefaultStringCompareOptions), + new SqlMetaData("nvarchar", SqlDbType.NVarChar, MaxUnicodeLength, 0, 0, 0, DefaultStringCompareOptions), + new SqlMetaData("real", SqlDbType.Real, 4, 24, 0, 0, SqlCompareOptions.None), + new SqlMetaData("uniqueidentifier", SqlDbType.UniqueIdentifier, 16, 0, 0, 0, SqlCompareOptions.None), + new SqlMetaData("smalldatetime", SqlDbType.SmallDateTime, 4, 16, 0, 0, SqlCompareOptions.None), + new SqlMetaData("smallint", SqlDbType.SmallInt, 2, 5, 0, 0, SqlCompareOptions.None), + new SqlMetaData("smallmoney", SqlDbType.SmallMoney, 4, 10, 4, 0, SqlCompareOptions.None), + new SqlMetaData("text", SqlDbType.Text, UnlimitedMaxLength, 0, 0, 0, DefaultStringCompareOptions), + new SqlMetaData("timestamp", SqlDbType.Timestamp, 8, 0, 0, 0, SqlCompareOptions.None), + new SqlMetaData("tinyint", SqlDbType.TinyInt, 1, 3, 0, 0, SqlCompareOptions.None), + new SqlMetaData("varbinary", SqlDbType.VarBinary, MaxBinaryLength, 0, 0, 0, SqlCompareOptions.None), + new SqlMetaData("varchar", SqlDbType.VarChar, MaxANSILength, 0, 0, 0, DefaultStringCompareOptions), + new SqlMetaData("sql_variant", SqlDbType.Variant, 8016, 0, 0, 0, SqlCompareOptions.None), + new SqlMetaData("nvarchar", SqlDbType.NVarChar, 1, 0, 0, 0, DefaultStringCompareOptions), + new SqlMetaData("xml", SqlDbType.Xml, UnlimitedMaxLength, 0, 0, 0, DefaultStringCompareOptions), + new SqlMetaData("nvarchar", SqlDbType.NVarChar, 1, 0, 0, 0, DefaultStringCompareOptions), + new SqlMetaData("nvarchar", SqlDbType.NVarChar, MaxUnicodeLength, 0, 0, 0, DefaultStringCompareOptions), + new SqlMetaData("nvarchar", SqlDbType.NVarChar, MaxUnicodeLength, 0, 0, 0, DefaultStringCompareOptions), + new SqlMetaData("udt", SqlDbType.Udt, 0, 0, 0, 0, SqlCompareOptions.None), + new SqlMetaData("table", SqlDbType.Structured, 0, 0, 0, 0, SqlCompareOptions.None), + new SqlMetaData("date", SqlDbType.Date, 3, 10, 0, 0, SqlCompareOptions.None), + new SqlMetaData("time", SqlDbType.Time, 5, 0, 7, 0, SqlCompareOptions.None), + new SqlMetaData("datetime2", SqlDbType.DateTime2, 8, 0, 7, 0, SqlCompareOptions.None), + new SqlMetaData("datetimeoffset", SqlDbType.DateTimeOffset, 10, 0, 7, 0, SqlCompareOptions.None), + }; private string _name; private long _maxLength; @@ -183,7 +147,6 @@ public sealed partial class SqlMetaData private string _xmlSchemaCollectionOwningSchema; private string _xmlSchemaCollectionName; private string _serverTypeName; - private bool _partialLength; private Type _udtType; private bool _useServerDefault; private bool _isUniqueKey; @@ -456,50 +419,15 @@ public SqlMetaData(string name, SqlDbType dbType, string database, string owning Construct(name, dbType, database, owningSchema, objectName, DefaultUseServerDefault, DefaultIsUniqueKey, DefaultColumnSortOrder, DefaultSortOrdinal); } - // Most general constructor, should be able to initialize all SqlMetaData fields.(Used by SqlParameter) - internal SqlMetaData( - string name, - SqlDbType sqlDBType, - long maxLength, - byte precision, - byte scale, - long localeId, - SqlCompareOptions compareOptions, - string xmlSchemaCollectionDatabase, - string xmlSchemaCollectionOwningSchema, - string xmlSchemaCollectionName, - bool partialLength, - Type udtType - ) - { - AssertNameIsValid(name); - - _name = name; - _sqlDbType = sqlDBType; - _maxLength = maxLength; - _precision = precision; - _scale = scale; - _locale = localeId; - _compareOptions = compareOptions; - _xmlSchemaCollectionDatabase = xmlSchemaCollectionDatabase; - _xmlSchemaCollectionOwningSchema = xmlSchemaCollectionOwningSchema; - _xmlSchemaCollectionName = xmlSchemaCollectionName; - _partialLength = partialLength; - _udtType = udtType; - } - // Private constructor used to initialize default instance array elements. // DO NOT EXPOSE OUTSIDE THIS CLASS! It performs no validation. - private SqlMetaData( - string name, + private SqlMetaData(string name, SqlDbType sqlDbType, long maxLength, byte precision, byte scale, long localeId, - SqlCompareOptions compareOptions, - bool partialLength - ) + SqlCompareOptions compareOptions) { AssertNameIsValid(name); @@ -510,7 +438,6 @@ bool partialLength _scale = scale; _locale = localeId; _compareOptions = compareOptions; - _partialLength = partialLength; _udtType = null; } @@ -587,8 +514,6 @@ public string TypeName /// public string XmlSchemaCollectionOwningSchema => _xmlSchemaCollectionOwningSchema; - internal bool IsPartialLength => _partialLength; - internal string UdtTypeName { get @@ -2048,32 +1973,6 @@ public char[] Adjust(char[] value) return value; } - - internal static SqlMetaData GetPartialLengthMetaData(SqlMetaData md) - { - if (md.IsPartialLength == true) - { - return md; - } - if (md.SqlDbType == SqlDbType.Xml) - { - ThrowInvalidType(); //Xml should always have IsPartialLength = true - } - if ( - md.SqlDbType == SqlDbType.NVarChar || - md.SqlDbType == SqlDbType.VarChar || - md.SqlDbType == SqlDbType.VarBinary - ) - { - return new SqlMetaData(md.Name, md.SqlDbType, SqlMetaData.Max, 0, 0, md.LocaleId, - md.CompareOptions, null, null, null, true, md.Type); - } - else - { - return md; - } - } - private static void ThrowInvalidType() { throw ADP.InvalidMetaDataValue(); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlSer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlSer.cs index f25aff0135..c10b5779f1 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlSer.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/SqlSer.cs @@ -12,11 +12,8 @@ namespace Microsoft.Data.SqlClient.Server { - internal sealed class SerializationHelperSql9 + internal static class SerializationHelperSql9 { - // Don't let anyone create an instance of this class. - private SerializationHelperSql9() { } - // Get the m_size of the serialized stream for this type, in bytes. // This method creates an instance of the type using the public // no-argument constructor, serializes it, and returns the m_size diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/TriggerAction.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/TriggerAction.netfx.cs new file mode 100644 index 0000000000..fd57cedc72 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/TriggerAction.netfx.cs @@ -0,0 +1,263 @@ +// 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. + +#if NETFRAMEWORK + +namespace Microsoft.Data.SqlClient.Server +{ + /// + public enum TriggerAction + { + /// + Invalid = 0, // Not actually a valid type: not persisted. + + /// + Insert = 1, + + /// + Update = 2, + + /// + Delete = 3, + + /// + CreateTable = 21, + + /// + AlterTable = 22, + + /// + DropTable = 23, + + /// + CreateIndex = 24, + + /// + AlterIndex = 25, + + /// + DropIndex = 26, + + /// + CreateSecurityExpression = 31, + + /// + DropSecurityExpression = 33, + + /// + CreateSynonym = 34, + + /// + DropSynonym = 36, + + /// + CreateView = 41, + + /// + AlterView = 42, + + /// + DropView = 43, + + /// + CreateProcedure = 51, + + /// + AlterProcedure = 52, + + /// + DropProcedure = 53, + + /// + CreateFunction = 61, + + /// + AlterFunction = 62, + + /// + DropFunction = 63, + + /// + CreateTrigger = 71, + + /// + AlterTrigger = 72, + + /// + DropTrigger = 73, + + /// + CreateEventNotification = 74, + + /// + DropEventNotification = 76, + + /// + CreateType = 91, + + // AlterType = 92, + + /// + DropType = 93, + + /// + CreateAssembly = 101, + + /// + AlterAssembly = 102, + + /// + DropAssembly = 103, + + /// + CreateUser = 131, + + /// + AlterUser = 132, + + /// + DropUser = 133, + + /// + CreateRole = 134, + + /// + AlterRole = 135, + + /// + DropRole = 136, + + /// + CreateAppRole = 137, + + /// + AlterAppRole = 138, + + /// + DropAppRole = 139, + + /// + CreateSchema = 141, + + /// + AlterSchema = 142, + + /// + DropSchema = 143, + + /// + CreateLogin = 144, + + /// + AlterLogin = 145, + + /// + DropLogin = 146, + + /// + CreateMsgType = 151, + + // AlterMsgType = 152 + + /// + DropMsgType = 153, + + /// + CreateContract = 154, + + // AlterContract = 155 + + /// + DropContract = 156, + + /// + CreateQueue = 157, + + /// + AlterQueue = 158, + + /// + DropQueue = 159, + + /// + CreateService = 161, + + /// + AlterService = 162, + + /// + DropService = 163, + + /// + CreateRoute = 164, + + /// + AlterRoute = 165, + + /// + DropRoute = 166, + + /// + GrantStatement = 167, + + /// + DenyStatement = 168, + + /// + RevokeStatement = 169, + + /// + GrantObject = 170, + + /// + DenyObject = 171, + + /// + RevokeObject = 172, + + /// + CreateBinding = 174, + + /// + AlterBinding = 175, + + /// + DropBinding = 176, + + // CreateXmlSchema = 177 + // AlterXmlSchema = 178 + // DropXmlSchema = 179 + // CreateHttpEndpoint = 181 + // AlterHttpEndpoint = 182 + // DropHttpEndpoint = 183 + + /// + CreatePartitionFunction = 191, + + /// + AlterPartitionFunction = 192, + + /// + DropPartitionFunction = 193, + + /// + CreatePartitionScheme = 194, + + /// + AlterPartitionScheme = 195, + + /// + DropPartitionScheme = 196, + + // CreateDatabase = 201 + // AlterDatabase = 202 + // DropDatabase = 203 + + // 1000-1999 is reserved for SqlTrace + // TraceStart = 1000 + // TraceEnd = 1999 + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.cs index bc259585ff..39b2494fa0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.cs @@ -27,7 +27,7 @@ namespace Microsoft.Data.SqlClient.Server // // These are all based off of knowing the clr type of the value // as an ExtendedClrTypeCode enum for rapid access (lookup in static array is best, if possible). - internal static partial class ValueUtilsSmi + internal static class ValueUtilsSmi { private const int MaxByteChunkSize = TdsEnums.MAXSIZE; private const int MaxCharChunkSize = TdsEnums.MAXSIZE / sizeof(char); @@ -51,20 +51,20 @@ internal static partial class ValueUtilsSmi // 1) go directly to the source for the requested type if possible // 2) instantiate the value based on the stored type (GetValue), then ask the Clr // to convert. - internal static bool IsDBNull(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + internal static bool IsDBNull(ITypedGettersV3 getters, int ordinal) { - return IsDBNull_Unchecked(sink, getters, ordinal); + return IsDBNull_Unchecked(getters, ordinal); } - internal static bool GetBoolean(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static bool GetBoolean(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { - ThrowIfITypedGettersIsNull(sink, getters, ordinal); + ThrowIfITypedGettersIsNull(getters, ordinal); if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.Boolean)) { - return GetBoolean_Unchecked(sink, getters, ordinal); + return GetBoolean_Unchecked(getters, ordinal); } - object result = GetValue(sink, getters, ordinal, metaData); + object result = GetValue(getters, ordinal, metaData); if (result == null) { throw ADP.InvalidCast(); @@ -72,14 +72,14 @@ internal static bool GetBoolean(SmiEventSink_Default sink, ITypedGettersV3 gette return (bool)result; } - internal static byte GetByte(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static byte GetByte(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { - ThrowIfITypedGettersIsNull(sink, getters, ordinal); + ThrowIfITypedGettersIsNull(getters, ordinal); if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.Byte)) { - return GetByte_Unchecked(sink, getters, ordinal); + return GetByte_Unchecked(getters, ordinal); } - object result = GetValue(sink, getters, ordinal, metaData); + object result = GetValue(getters, ordinal, metaData); if (result == null) { throw ADP.InvalidCast(); @@ -87,9 +87,9 @@ internal static byte GetByte(SmiEventSink_Default sink, ITypedGettersV3 getters, return (byte)result; } - private static long GetBytesConversion(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData, long fieldOffset, byte[] buffer, int bufferOffset, int length, bool throwOnNull) + private static long GetBytesConversion(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData, long fieldOffset, byte[] buffer, int bufferOffset, int length, bool throwOnNull) { - object obj = GetSqlValue(sink, getters, ordinal, metaData); + object obj = GetSqlValue(getters, ordinal, metaData); if (obj== null) { throw ADP.InvalidCast(); @@ -120,7 +120,7 @@ private static long GetBytesConversion(SmiEventSink_Default sink, ITypedGettersV return length; } - internal static long GetBytes(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiExtendedMetaData metaData, long fieldOffset, byte[] buffer, int bufferOffset, int length, bool throwOnNull) + internal static long GetBytes(ITypedGettersV3 getters, int ordinal, SmiExtendedMetaData metaData, long fieldOffset, byte[] buffer, int bufferOffset, int length, bool throwOnNull) { // Additional exclusions not caught by GetBytesInternal if ( @@ -140,15 +140,15 @@ internal static long GetBytes(SmiEventSink_Default sink, ITypedGettersV3 getters } else { - return GetBytesInternal(sink, getters, ordinal, metaData, fieldOffset, buffer, bufferOffset, length, throwOnNull); + return GetBytesInternal(getters, ordinal, metaData, fieldOffset, buffer, bufferOffset, length, throwOnNull); } } - internal static long GetBytesInternal(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData, long fieldOffset, byte[] buffer, int bufferOffset, int length, bool throwOnNull) + internal static long GetBytesInternal(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData, long fieldOffset, byte[] buffer, int bufferOffset, int length, bool throwOnNull) { if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.ByteArray)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { if (throwOnNull) { @@ -163,7 +163,7 @@ internal static long GetBytesInternal(SmiEventSink_Default sink, ITypedGettersV3 return 0; } } - long actualLength = GetBytesLength_Unchecked(sink, getters, ordinal); + long actualLength = GetBytesLength_Unchecked(getters, ordinal); if (buffer == null) { return actualLength; @@ -181,20 +181,20 @@ internal static long GetBytesInternal(SmiEventSink_Default sink, ITypedGettersV3 Debug.Assert(length >= 0, "Invalid CheckXetParameters return length!"); if (length > 0) { - length = GetBytes_Unchecked(sink, getters, ordinal, fieldOffset, buffer, bufferOffset, length); + length = GetBytes_Unchecked(getters, ordinal, fieldOffset, buffer, bufferOffset, length); } return length; } - return GetBytesConversion(sink, getters, ordinal, metaData, fieldOffset, buffer, bufferOffset, length, throwOnNull); + return GetBytesConversion(getters, ordinal, metaData, fieldOffset, buffer, bufferOffset, length, throwOnNull); } - internal static long GetChars(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData, long fieldOffset, char[] buffer, int bufferOffset, int length) + internal static long GetChars(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData, long fieldOffset, char[] buffer, int bufferOffset, int length) { - ThrowIfITypedGettersIsNull(sink, getters, ordinal); + ThrowIfITypedGettersIsNull(getters, ordinal); if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.CharArray)) { - long actualLength = GetCharsLength_Unchecked(sink, getters, ordinal); + long actualLength = GetCharsLength_Unchecked(getters, ordinal); if (buffer == null) { return actualLength; @@ -203,12 +203,12 @@ internal static long GetChars(SmiEventSink_Default sink, ITypedGettersV3 getters Debug.Assert(length >= 0, "Buffer.Length was invalid!"); if (length > 0) { - length = GetChars_Unchecked(sink, getters, ordinal, fieldOffset, buffer, bufferOffset, length); + length = GetChars_Unchecked(getters, ordinal, fieldOffset, buffer, bufferOffset, length); } return length; } - string value = (string)GetValue(sink, getters, ordinal, metaData); + string value = (string)GetValue(getters, ordinal, metaData); if (value == null) { throw ADP.InvalidCast(); @@ -224,14 +224,14 @@ internal static long GetChars(SmiEventSink_Default sink, ITypedGettersV3 getters return length; } - internal static DateTime GetDateTime(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static DateTime GetDateTime(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { - ThrowIfITypedGettersIsNull(sink, getters, ordinal); + ThrowIfITypedGettersIsNull(getters, ordinal); if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.DateTime)) { - return GetDateTime_Unchecked(sink, getters, ordinal); + return GetDateTime_Unchecked(getters, ordinal); } - object result = GetValue(sink, getters, ordinal, metaData); + object result = GetValue(getters, ordinal, metaData); if (result == null) { throw ADP.InvalidCast(); @@ -239,41 +239,25 @@ internal static DateTime GetDateTime(SmiEventSink_Default sink, ITypedGettersV3 return (DateTime)result; } - // calling GetDateTimeOffset on possibly v100 SMI - internal static DateTimeOffset GetDateTimeOffset(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData, bool gettersSupport2008DateTime) - { - if (gettersSupport2008DateTime) - { - return GetDateTimeOffset(sink, (SmiTypedGetterSetter)getters, ordinal, metaData); - } - ThrowIfITypedGettersIsNull(sink, getters, ordinal); - object result = GetValue(sink, getters, ordinal, metaData); - if (result == null) - { - throw ADP.InvalidCast(); - } - return (DateTimeOffset)result; - } - // dealing with v200 SMI - internal static DateTimeOffset GetDateTimeOffset(SmiEventSink_Default sink, SmiTypedGetterSetter getters, int ordinal, SmiMetaData metaData) + internal static DateTimeOffset GetDateTimeOffset(SmiTypedGetterSetter getters, int ordinal, SmiMetaData metaData) { - ThrowIfITypedGettersIsNull(sink, getters, ordinal); + ThrowIfITypedGettersIsNull(getters, ordinal); if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.DateTimeOffset)) { - return GetDateTimeOffset_Unchecked(sink, getters, ordinal); + return GetDateTimeOffset_Unchecked(getters, ordinal); } - return (DateTimeOffset)GetValue200(sink, getters, ordinal, metaData, null); + return (DateTimeOffset)GetValue200(getters, ordinal, metaData); } - internal static decimal GetDecimal(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static decimal GetDecimal(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { - ThrowIfITypedGettersIsNull(sink, getters, ordinal); + ThrowIfITypedGettersIsNull(getters, ordinal); if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.Decimal)) { - return GetDecimal_PossiblyMoney(sink, getters, ordinal, metaData); + return GetDecimal_PossiblyMoney(getters, ordinal, metaData); } - object result = GetValue(sink, getters, ordinal, metaData); + object result = GetValue(getters, ordinal, metaData); if (result == null) { throw ADP.InvalidCast(); @@ -281,14 +265,14 @@ internal static decimal GetDecimal(SmiEventSink_Default sink, ITypedGettersV3 ge return (decimal)result; } - internal static double GetDouble(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static double GetDouble(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { - ThrowIfITypedGettersIsNull(sink, getters, ordinal); + ThrowIfITypedGettersIsNull(getters, ordinal); if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.Double)) { - return GetDouble_Unchecked(sink, getters, ordinal); + return GetDouble_Unchecked(getters, ordinal); } - object result = GetValue(sink, getters, ordinal, metaData); + object result = GetValue(getters, ordinal, metaData); if (result == null) { throw ADP.InvalidCast(); @@ -296,14 +280,14 @@ internal static double GetDouble(SmiEventSink_Default sink, ITypedGettersV3 gett return (double)result; } - internal static Guid GetGuid(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static Guid GetGuid(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { - ThrowIfITypedGettersIsNull(sink, getters, ordinal); + ThrowIfITypedGettersIsNull(getters, ordinal); if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.Guid)) { - return GetGuid_Unchecked(sink, getters, ordinal); + return GetGuid_Unchecked(getters, ordinal); } - object result = GetValue(sink, getters, ordinal, metaData); + object result = GetValue(getters, ordinal, metaData); if (result == null) { throw ADP.InvalidCast(); @@ -311,14 +295,14 @@ internal static Guid GetGuid(SmiEventSink_Default sink, ITypedGettersV3 getters, return (Guid)result; } - internal static short GetInt16(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static short GetInt16(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { - ThrowIfITypedGettersIsNull(sink, getters, ordinal); + ThrowIfITypedGettersIsNull(getters, ordinal); if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.Int16)) { - return GetInt16_Unchecked(sink, getters, ordinal); + return GetInt16_Unchecked(getters, ordinal); } - object obj = GetValue(sink, getters, ordinal, metaData); + object obj = GetValue(getters, ordinal, metaData); if (obj == null) { throw ADP.InvalidCast(); @@ -326,14 +310,14 @@ internal static short GetInt16(SmiEventSink_Default sink, ITypedGettersV3 getter return (short)obj; } - internal static int GetInt32(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static int GetInt32(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { - ThrowIfITypedGettersIsNull(sink, getters, ordinal); + ThrowIfITypedGettersIsNull(getters, ordinal); if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.Int32)) { - return GetInt32_Unchecked(sink, getters, ordinal); + return GetInt32_Unchecked(getters, ordinal); } - object result = GetValue(sink, getters, ordinal, metaData); + object result = GetValue(getters, ordinal, metaData); if (result == null) { throw ADP.InvalidCast(); @@ -341,14 +325,14 @@ internal static int GetInt32(SmiEventSink_Default sink, ITypedGettersV3 getters, return (int)result; } - internal static long GetInt64(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static long GetInt64(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { - ThrowIfITypedGettersIsNull(sink, getters, ordinal); + ThrowIfITypedGettersIsNull(getters, ordinal); if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.Int64)) { - return GetInt64_Unchecked(sink, getters, ordinal); + return GetInt64_Unchecked(getters, ordinal); } - object result = GetValue(sink, getters, ordinal, metaData); + object result = GetValue(getters, ordinal, metaData); if (result == null) { throw ADP.InvalidCast(); @@ -356,14 +340,14 @@ internal static long GetInt64(SmiEventSink_Default sink, ITypedGettersV3 getters return (long)result; } - internal static float GetSingle(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static float GetSingle(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { - ThrowIfITypedGettersIsNull(sink, getters, ordinal); + ThrowIfITypedGettersIsNull(getters, ordinal); if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.Single)) { - return GetSingle_Unchecked(sink, getters, ordinal); + return GetSingle_Unchecked(getters, ordinal); } - object result = GetValue(sink, getters, ordinal, metaData); + object result = GetValue(getters, ordinal, metaData); if (result == null) { throw ADP.InvalidCast(); @@ -371,17 +355,17 @@ internal static float GetSingle(SmiEventSink_Default sink, ITypedGettersV3 gette return (float)result; } - internal static SqlBinary GetSqlBinary(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static SqlBinary GetSqlBinary(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.SqlBinary)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { return SqlBinary.Null; } - return GetSqlBinary_Unchecked(sink, getters, ordinal); + return GetSqlBinary_Unchecked(getters, ordinal); } - object result = GetSqlValue(sink, getters, ordinal, metaData); + object result = GetSqlValue(getters, ordinal, metaData); if (result == null) { throw ADP.InvalidCast(); @@ -389,17 +373,17 @@ internal static SqlBinary GetSqlBinary(SmiEventSink_Default sink, ITypedGettersV return (SqlBinary)result; } - internal static SqlBoolean GetSqlBoolean(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static SqlBoolean GetSqlBoolean(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.SqlBoolean)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { return SqlBoolean.Null; } - return new SqlBoolean(GetBoolean_Unchecked(sink, getters, ordinal)); + return new SqlBoolean(GetBoolean_Unchecked(getters, ordinal)); } - object result = GetSqlValue(sink, getters, ordinal, metaData); + object result = GetSqlValue(getters, ordinal, metaData); if (result == null) { throw ADP.InvalidCast(); @@ -407,17 +391,17 @@ internal static SqlBoolean GetSqlBoolean(SmiEventSink_Default sink, ITypedGetter return (SqlBoolean)result; } - internal static SqlByte GetSqlByte(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static SqlByte GetSqlByte(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.SqlByte)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { return SqlByte.Null; } - return new SqlByte(GetByte_Unchecked(sink, getters, ordinal)); + return new SqlByte(GetByte_Unchecked(getters, ordinal)); } - object result = GetSqlValue(sink, getters, ordinal, metaData); + object result = GetSqlValue(getters, ordinal, metaData); if (result == null) { throw ADP.InvalidCast(); @@ -425,34 +409,37 @@ internal static SqlByte GetSqlByte(SmiEventSink_Default sink, ITypedGettersV3 ge return (SqlByte)result; } - internal static SqlBytes GetSqlBytes(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData, SmiContext context) + internal static SqlBytes GetSqlBytes( + ITypedGettersV3 getters, + int ordinal, + SmiMetaData metaData) { SqlBytes result; if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.SqlBytes)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { result = SqlBytes.Null; } else { - long length = GetBytesLength_Unchecked(sink, getters, ordinal); + long length = GetBytesLength_Unchecked(getters, ordinal); if (length >= 0 && length < MaxByteChunkSize) { - byte[] byteBuffer = GetByteArray_Unchecked(sink, getters, ordinal); + byte[] byteBuffer = GetByteArray_Unchecked(getters, ordinal); result = new SqlBytes(byteBuffer); } else { - Stream s = new SmiGettersStream(sink, getters, ordinal, metaData); - s = CopyIntoNewSmiScratchStream(s, sink, context); + Stream s = new SmiGettersStream(getters, ordinal, metaData); + s = CopyIntoNewSmiScratchStream(s); result = new SqlBytes(s); } } } else { - object obj = GetSqlValue(sink, getters, ordinal, metaData); + object obj = GetSqlValue(getters, ordinal, metaData); if (obj == null) { throw ADP.InvalidCast(); @@ -471,39 +458,22 @@ internal static SqlBytes GetSqlBytes(SmiEventSink_Default sink, ITypedGettersV3 return result; } - internal static SqlChars GetSqlChars(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData, SmiContext context) + internal static SqlChars GetSqlChars( + ITypedGettersV3 getters, + int ordinal, + SmiMetaData metaData) { SqlChars result; if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.SqlChars)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { result = SqlChars.Null; } else { -#if NETFRAMEWORK - long length = GetCharsLength_Unchecked(sink, getters, ordinal); - if (length < MaxByteChunkSize || !InOutOfProcHelper.InProc) - { - char[] charBuffer = GetCharArray_Unchecked(sink, getters, ordinal); - result = new SqlChars(charBuffer); - } - else - { // InProc only - Stream s = new SmiGettersStream(sink, getters, ordinal, metaData); - SqlStreamChars sc = CopyIntoNewSmiScratchStreamChars(s, sink, context); - - Type SqlCharsType = (typeof(SqlChars)); - Type[] argTypes = new Type[] { typeof(SqlStreamChars) }; - SqlChars SqlCharsInstance = (SqlChars)SqlCharsType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, - null, argTypes, null).Invoke(new object[] { sc }); - result = SqlCharsInstance; - } -#else - char[] charBuffer = GetCharArray_Unchecked(sink, getters, ordinal); + char[] charBuffer = GetCharArray_Unchecked(getters, ordinal); result = new SqlChars(charBuffer); -#endif } } else @@ -511,7 +481,7 @@ internal static SqlChars GetSqlChars(SmiEventSink_Default sink, ITypedGettersV3 SqlString stringValue; if (metaData.SqlDbType == SqlDbType.Xml) { - SqlXml xmlValue = GetSqlXml_Unchecked(sink, getters, ordinal, null); + SqlXml xmlValue = GetSqlXml_Unchecked(getters, ordinal); if (xmlValue.IsNull) { @@ -524,7 +494,7 @@ internal static SqlChars GetSqlChars(SmiEventSink_Default sink, ITypedGettersV3 } else { - object obj = GetSqlValue(sink, getters, ordinal, metaData); + object obj = GetSqlValue(getters, ordinal, metaData); if (obj == null) { throw ADP.InvalidCast(); @@ -545,24 +515,24 @@ internal static SqlChars GetSqlChars(SmiEventSink_Default sink, ITypedGettersV3 return result; } - internal static SqlDateTime GetSqlDateTime(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static SqlDateTime GetSqlDateTime(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { SqlDateTime result; if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.SqlDateTime)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { result = SqlDateTime.Null; } else { - DateTime temp = GetDateTime_Unchecked(sink, getters, ordinal); + DateTime temp = GetDateTime_Unchecked(getters, ordinal); result = new SqlDateTime(temp); } } else { - object obj = GetSqlValue(sink, getters, ordinal, metaData); + object obj = GetSqlValue(getters, ordinal, metaData); if (obj == null) { throw ADP.InvalidCast(); @@ -573,23 +543,23 @@ internal static SqlDateTime GetSqlDateTime(SmiEventSink_Default sink, ITypedGett return result; } - internal static SqlDecimal GetSqlDecimal(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static SqlDecimal GetSqlDecimal(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { SqlDecimal result; if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.SqlDecimal)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { result = SqlDecimal.Null; } else { - result = GetSqlDecimal_Unchecked(sink, getters, ordinal); + result = GetSqlDecimal_Unchecked(getters, ordinal); } } else { - object obj = GetSqlValue(sink, getters, ordinal, metaData); + object obj = GetSqlValue(getters, ordinal, metaData); if (obj == null) { throw ADP.InvalidCast(); @@ -600,24 +570,24 @@ internal static SqlDecimal GetSqlDecimal(SmiEventSink_Default sink, ITypedGetter return result; } - internal static SqlDouble GetSqlDouble(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static SqlDouble GetSqlDouble(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { SqlDouble result; if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.SqlDouble)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { result = SqlDouble.Null; } else { - double temp = GetDouble_Unchecked(sink, getters, ordinal); + double temp = GetDouble_Unchecked(getters, ordinal); result = new SqlDouble(temp); } } else { - object obj = GetSqlValue(sink, getters, ordinal, metaData); + object obj = GetSqlValue(getters, ordinal, metaData); if (obj == null) { throw ADP.InvalidCast(); @@ -628,24 +598,24 @@ internal static SqlDouble GetSqlDouble(SmiEventSink_Default sink, ITypedGettersV return result; } - internal static SqlGuid GetSqlGuid(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static SqlGuid GetSqlGuid(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { SqlGuid result; if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.SqlGuid)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { result = SqlGuid.Null; } else { - Guid temp = GetGuid_Unchecked(sink, getters, ordinal); + Guid temp = GetGuid_Unchecked(getters, ordinal); result = new SqlGuid(temp); } } else { - object obj = GetSqlValue(sink, getters, ordinal, metaData); + object obj = GetSqlValue(getters, ordinal, metaData); if (obj == null) { throw ADP.InvalidCast(); @@ -656,24 +626,24 @@ internal static SqlGuid GetSqlGuid(SmiEventSink_Default sink, ITypedGettersV3 ge return result; } - internal static SqlInt16 GetSqlInt16(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static SqlInt16 GetSqlInt16(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { SqlInt16 result; if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.SqlInt16)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { result = SqlInt16.Null; } else { - short temp = GetInt16_Unchecked(sink, getters, ordinal); + short temp = GetInt16_Unchecked(getters, ordinal); result = new SqlInt16(temp); } } else { - object obj = GetSqlValue(sink, getters, ordinal, metaData); + object obj = GetSqlValue(getters, ordinal, metaData); if (obj == null) { throw ADP.InvalidCast(); @@ -684,24 +654,24 @@ internal static SqlInt16 GetSqlInt16(SmiEventSink_Default sink, ITypedGettersV3 return result; } - internal static SqlInt32 GetSqlInt32(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static SqlInt32 GetSqlInt32(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { SqlInt32 result; if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.SqlInt32)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { result = SqlInt32.Null; } else { - int temp = GetInt32_Unchecked(sink, getters, ordinal); + int temp = GetInt32_Unchecked(getters, ordinal); result = new SqlInt32(temp); } } else { - object obj = GetSqlValue(sink, getters, ordinal, metaData); + object obj = GetSqlValue(getters, ordinal, metaData); if (obj == null) { throw ADP.InvalidCast(); @@ -711,24 +681,24 @@ internal static SqlInt32 GetSqlInt32(SmiEventSink_Default sink, ITypedGettersV3 return result; } - internal static SqlInt64 GetSqlInt64(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static SqlInt64 GetSqlInt64(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { SqlInt64 result; if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.SqlInt64)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { result = SqlInt64.Null; } else { - long temp = GetInt64_Unchecked(sink, getters, ordinal); + long temp = GetInt64_Unchecked(getters, ordinal); result = new SqlInt64(temp); } } else { - object obj = GetSqlValue(sink, getters, ordinal, metaData); + object obj = GetSqlValue(getters, ordinal, metaData); if (obj == null) { throw ADP.InvalidCast(); @@ -739,23 +709,23 @@ internal static SqlInt64 GetSqlInt64(SmiEventSink_Default sink, ITypedGettersV3 return result; } - internal static SqlMoney GetSqlMoney(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static SqlMoney GetSqlMoney(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { SqlMoney result; if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.SqlMoney)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { result = SqlMoney.Null; } else { - result = GetSqlMoney_Unchecked(sink, getters, ordinal); + result = GetSqlMoney_Unchecked(getters, ordinal); } } else { - object obj = GetSqlValue(sink, getters, ordinal, metaData); + object obj = GetSqlValue(getters, ordinal, metaData); if (obj == null) { throw ADP.InvalidCast(); @@ -766,24 +736,24 @@ internal static SqlMoney GetSqlMoney(SmiEventSink_Default sink, ITypedGettersV3 return result; } - internal static SqlSingle GetSqlSingle(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static SqlSingle GetSqlSingle(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { SqlSingle result; if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.SqlSingle)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { result = SqlSingle.Null; } else { - float temp = GetSingle_Unchecked(sink, getters, ordinal); + float temp = GetSingle_Unchecked(getters, ordinal); result = new SqlSingle(temp); } } else { - object obj = GetSqlValue(sink, getters, ordinal, metaData); + object obj = GetSqlValue(getters, ordinal, metaData); if (obj == null) { throw ADP.InvalidCast(); @@ -794,24 +764,24 @@ internal static SqlSingle GetSqlSingle(SmiEventSink_Default sink, ITypedGettersV return result; } - internal static SqlString GetSqlString(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static SqlString GetSqlString(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { SqlString result; if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.SqlString)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { result = SqlString.Null; } else { - string temp = GetString_Unchecked(sink, getters, ordinal); + string temp = GetString_Unchecked(getters, ordinal); result = new SqlString(temp); } } else if (SqlDbType.Xml == metaData.SqlDbType) { - SqlXml xmlValue = GetSqlXml_Unchecked(sink, getters, ordinal, null); + SqlXml xmlValue = GetSqlXml_Unchecked(getters, ordinal); if (xmlValue.IsNull) { @@ -824,7 +794,7 @@ internal static SqlString GetSqlString(SmiEventSink_Default sink, ITypedGettersV } else { - object obj = GetSqlValue(sink, getters, ordinal, metaData); + object obj = GetSqlValue(getters, ordinal, metaData); if (obj == null) { throw ADP.InvalidCast(); @@ -835,23 +805,26 @@ internal static SqlString GetSqlString(SmiEventSink_Default sink, ITypedGettersV return result; } - internal static SqlXml GetSqlXml(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData, SmiContext context) + internal static SqlXml GetSqlXml( + ITypedGettersV3 getters, + int ordinal, + SmiMetaData metaData) { SqlXml result; if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.SqlXml)) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { result = SqlXml.Null; } else { - result = GetSqlXml_Unchecked(sink, getters, ordinal, context); + result = GetSqlXml_Unchecked(getters, ordinal); } } else { - object obj = GetSqlValue(sink, getters, ordinal, metaData); + object obj = GetSqlValue(getters, ordinal, metaData); if (obj == null) { throw ADP.InvalidCast(); @@ -862,14 +835,14 @@ internal static SqlXml GetSqlXml(SmiEventSink_Default sink, ITypedGettersV3 gett return result; } - internal static string GetString(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + internal static string GetString(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { - ThrowIfITypedGettersIsNull(sink, getters, ordinal); + ThrowIfITypedGettersIsNull(getters, ordinal); if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.String)) { - return GetString_Unchecked(sink, getters, ordinal); + return GetString_Unchecked(getters, ordinal); } - object obj = GetValue(sink, getters, ordinal, metaData); + object obj = GetValue(getters, ordinal, metaData); if (obj == null) { throw ADP.InvalidCast(); @@ -878,27 +851,24 @@ internal static string GetString(SmiEventSink_Default sink, ITypedGettersV3 gett } // dealing with v200 SMI - internal static TimeSpan GetTimeSpan(SmiEventSink_Default sink, SmiTypedGetterSetter getters, int ordinal, SmiMetaData metaData) + internal static TimeSpan GetTimeSpan(SmiTypedGetterSetter getters, int ordinal, SmiMetaData metaData) { - ThrowIfITypedGettersIsNull(sink, getters, ordinal); + ThrowIfITypedGettersIsNull(getters, ordinal); if (CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.TimeSpan)) { - return GetTimeSpan_Unchecked(sink, getters, ordinal); + return GetTimeSpan_Unchecked(getters, ordinal); } - return (TimeSpan)GetValue200(sink, getters, ordinal, metaData, null); + return (TimeSpan)GetValue200(getters, ordinal, metaData); } // GetValue() for v200 SMI (2008 Date/Time types) internal static object GetValue200( - SmiEventSink_Default sink, SmiTypedGetterSetter getters, int ordinal, - SmiMetaData metaData, - SmiContext context - ) + SmiMetaData metaData) { object result; - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { result = DBNull.Value; } @@ -907,23 +877,22 @@ SmiContext context switch (metaData.SqlDbType) { case SqlDbType.Variant: // Handle variants specifically for v200, since they could contain v200 types - metaData = getters.GetVariantType(sink, ordinal); - sink.ProcessMessagesAndThrow(); + metaData = getters.GetVariantType(ordinal); Debug.Assert(SqlDbType.Variant != metaData.SqlDbType, "Variant-within-variant causes endless recursion!"); - result = GetValue200(sink, getters, ordinal, metaData, context); + result = GetValue200(getters, ordinal, metaData); break; case SqlDbType.Date: case SqlDbType.DateTime2: - result = GetDateTime_Unchecked(sink, getters, ordinal); + result = GetDateTime_Unchecked(getters, ordinal); break; case SqlDbType.Time: - result = GetTimeSpan_Unchecked(sink, getters, ordinal); + result = GetTimeSpan_Unchecked(getters, ordinal); break; case SqlDbType.DateTimeOffset: - result = GetDateTimeOffset_Unchecked(sink, getters, ordinal); + result = GetDateTimeOffset_Unchecked(getters, ordinal); break; default: - result = GetValue(sink, getters, ordinal, metaData, context); + result = GetValue(getters, ordinal, metaData); break; } } @@ -935,15 +904,12 @@ SmiContext context // implements SqlClient 1.1-compatible GetValue() semantics for everything except output parameters internal static object GetValue( - SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, - SmiMetaData metaData, - SmiContext context = null - ) + SmiMetaData metaData) { object result = null; - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { result = DBNull.Value; } @@ -952,85 +918,84 @@ internal static object GetValue( switch (metaData.SqlDbType) { case SqlDbType.BigInt: - result = GetInt64_Unchecked(sink, getters, ordinal); + result = GetInt64_Unchecked(getters, ordinal); break; case SqlDbType.Binary: - result = GetByteArray_Unchecked(sink, getters, ordinal); + result = GetByteArray_Unchecked(getters, ordinal); break; case SqlDbType.Bit: - result = GetBoolean_Unchecked(sink, getters, ordinal); + result = GetBoolean_Unchecked(getters, ordinal); break; case SqlDbType.Char: - result = GetString_Unchecked(sink, getters, ordinal); + result = GetString_Unchecked(getters, ordinal); break; case SqlDbType.DateTime: - result = GetDateTime_Unchecked(sink, getters, ordinal); + result = GetDateTime_Unchecked(getters, ordinal); break; case SqlDbType.Decimal: - result = GetSqlDecimal_Unchecked(sink, getters, ordinal).Value; + result = GetSqlDecimal_Unchecked(getters, ordinal).Value; break; case SqlDbType.Float: - result = GetDouble_Unchecked(sink, getters, ordinal); + result = GetDouble_Unchecked(getters, ordinal); break; case SqlDbType.Image: - result = GetByteArray_Unchecked(sink, getters, ordinal); + result = GetByteArray_Unchecked(getters, ordinal); break; case SqlDbType.Int: - result = GetInt32_Unchecked(sink, getters, ordinal); + result = GetInt32_Unchecked(getters, ordinal); break; case SqlDbType.Money: - result = GetSqlMoney_Unchecked(sink, getters, ordinal).Value; + result = GetSqlMoney_Unchecked(getters, ordinal).Value; break; case SqlDbType.NChar: - result = GetString_Unchecked(sink, getters, ordinal); + result = GetString_Unchecked(getters, ordinal); break; case SqlDbType.NText: - result = GetString_Unchecked(sink, getters, ordinal); + result = GetString_Unchecked(getters, ordinal); break; case SqlDbType.NVarChar: - result = GetString_Unchecked(sink, getters, ordinal); + result = GetString_Unchecked(getters, ordinal); break; case SqlDbType.Real: - result = GetSingle_Unchecked(sink, getters, ordinal); + result = GetSingle_Unchecked(getters, ordinal); break; case SqlDbType.UniqueIdentifier: - result = GetGuid_Unchecked(sink, getters, ordinal); + result = GetGuid_Unchecked(getters, ordinal); break; case SqlDbType.SmallDateTime: - result = GetDateTime_Unchecked(sink, getters, ordinal); + result = GetDateTime_Unchecked(getters, ordinal); break; case SqlDbType.SmallInt: - result = GetInt16_Unchecked(sink, getters, ordinal); + result = GetInt16_Unchecked(getters, ordinal); break; case SqlDbType.SmallMoney: - result = GetSqlMoney_Unchecked(sink, getters, ordinal).Value; + result = GetSqlMoney_Unchecked(getters, ordinal).Value; break; case SqlDbType.Text: - result = GetString_Unchecked(sink, getters, ordinal); + result = GetString_Unchecked(getters, ordinal); break; case SqlDbType.Timestamp: - result = GetByteArray_Unchecked(sink, getters, ordinal); + result = GetByteArray_Unchecked(getters, ordinal); break; case SqlDbType.TinyInt: - result = GetByte_Unchecked(sink, getters, ordinal); + result = GetByte_Unchecked(getters, ordinal); break; case SqlDbType.VarBinary: - result = GetByteArray_Unchecked(sink, getters, ordinal); + result = GetByteArray_Unchecked(getters, ordinal); break; case SqlDbType.VarChar: - result = GetString_Unchecked(sink, getters, ordinal); + result = GetString_Unchecked(getters, ordinal); break; case SqlDbType.Variant: - metaData = getters.GetVariantType(sink, ordinal); - sink.ProcessMessagesAndThrow(); + metaData = getters.GetVariantType(ordinal); Debug.Assert(SqlDbType.Variant != metaData.SqlDbType, "Variant-within-variant causes endless recursion!"); - result = GetValue(sink, getters, ordinal, metaData, context); + result = GetValue(getters, ordinal, metaData); break; case SqlDbType.Xml: - result = GetSqlXml_Unchecked(sink, getters, ordinal, context).Value; + result = GetSqlXml_Unchecked(getters, ordinal).Value; break; case SqlDbType.Udt: - result = GetUdt_LengthChecked(sink, getters, ordinal, metaData); + result = GetUdt_LengthChecked(getters, ordinal, metaData); break; } } @@ -1040,15 +1005,12 @@ internal static object GetValue( // dealing with v200 SMI internal static object GetSqlValue200( - SmiEventSink_Default sink, SmiTypedGetterSetter getters, int ordinal, - SmiMetaData metaData, - SmiContext context = null - ) + SmiMetaData metaData) { object result; - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { if (metaData.SqlDbType == SqlDbType.Udt) { @@ -1064,23 +1026,22 @@ internal static object GetSqlValue200( switch (metaData.SqlDbType) { case SqlDbType.Variant: // Handle variants specifically for v200, since they could contain v200 types - metaData = getters.GetVariantType(sink, ordinal); - sink.ProcessMessagesAndThrow(); + metaData = getters.GetVariantType(ordinal); Debug.Assert(SqlDbType.Variant != metaData.SqlDbType, "Variant-within-variant causes endless recursion!"); - result = GetSqlValue200(sink, getters, ordinal, metaData, context); + result = GetSqlValue200(getters, ordinal, metaData); break; case SqlDbType.Date: case SqlDbType.DateTime2: - result = GetDateTime_Unchecked(sink, getters, ordinal); + result = GetDateTime_Unchecked(getters, ordinal); break; case SqlDbType.Time: - result = GetTimeSpan_Unchecked(sink, getters, ordinal); + result = GetTimeSpan_Unchecked(getters, ordinal); break; case SqlDbType.DateTimeOffset: - result = GetDateTimeOffset_Unchecked(sink, getters, ordinal); + result = GetDateTimeOffset_Unchecked(getters, ordinal); break; default: - result = GetSqlValue(sink, getters, ordinal, metaData, context); + result = GetSqlValue(getters, ordinal, metaData); break; } } @@ -1090,15 +1051,12 @@ internal static object GetSqlValue200( // implements SqlClient 1.1-compatible GetSqlValue() semantics for everything except output parameters internal static object GetSqlValue( - SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, - SmiMetaData metaData, - SmiContext context = null - ) + SmiMetaData metaData) { object result = null; - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { if ( metaData.SqlDbType ==SqlDbType.Udt) { @@ -1114,85 +1072,84 @@ internal static object GetSqlValue( switch (metaData.SqlDbType) { case SqlDbType.BigInt: - result = new SqlInt64(GetInt64_Unchecked(sink, getters, ordinal)); + result = new SqlInt64(GetInt64_Unchecked(getters, ordinal)); break; case SqlDbType.Binary: - result = GetSqlBinary_Unchecked(sink, getters, ordinal); + result = GetSqlBinary_Unchecked(getters, ordinal); break; case SqlDbType.Bit: - result = new SqlBoolean(GetBoolean_Unchecked(sink, getters, ordinal)); + result = new SqlBoolean(GetBoolean_Unchecked(getters, ordinal)); break; case SqlDbType.Char: - result = new SqlString(GetString_Unchecked(sink, getters, ordinal)); + result = new SqlString(GetString_Unchecked(getters, ordinal)); break; case SqlDbType.DateTime: - result = new SqlDateTime(GetDateTime_Unchecked(sink, getters, ordinal)); + result = new SqlDateTime(GetDateTime_Unchecked(getters, ordinal)); break; case SqlDbType.Decimal: - result = GetSqlDecimal_Unchecked(sink, getters, ordinal); + result = GetSqlDecimal_Unchecked(getters, ordinal); break; case SqlDbType.Float: - result = new SqlDouble(GetDouble_Unchecked(sink, getters, ordinal)); + result = new SqlDouble(GetDouble_Unchecked(getters, ordinal)); break; case SqlDbType.Image: - result = GetSqlBinary_Unchecked(sink, getters, ordinal); + result = GetSqlBinary_Unchecked(getters, ordinal); break; case SqlDbType.Int: - result = new SqlInt32(GetInt32_Unchecked(sink, getters, ordinal)); + result = new SqlInt32(GetInt32_Unchecked(getters, ordinal)); break; case SqlDbType.Money: - result = GetSqlMoney_Unchecked(sink, getters, ordinal); + result = GetSqlMoney_Unchecked(getters, ordinal); break; case SqlDbType.NChar: - result = new SqlString(GetString_Unchecked(sink, getters, ordinal)); + result = new SqlString(GetString_Unchecked(getters, ordinal)); break; case SqlDbType.NText: - result = new SqlString(GetString_Unchecked(sink, getters, ordinal)); + result = new SqlString(GetString_Unchecked(getters, ordinal)); break; case SqlDbType.NVarChar: - result = new SqlString(GetString_Unchecked(sink, getters, ordinal)); + result = new SqlString(GetString_Unchecked(getters, ordinal)); break; case SqlDbType.Real: - result = new SqlSingle(GetSingle_Unchecked(sink, getters, ordinal)); + result = new SqlSingle(GetSingle_Unchecked(getters, ordinal)); break; case SqlDbType.UniqueIdentifier: - result = new SqlGuid(GetGuid_Unchecked(sink, getters, ordinal)); + result = new SqlGuid(GetGuid_Unchecked(getters, ordinal)); break; case SqlDbType.SmallDateTime: - result = new SqlDateTime(GetDateTime_Unchecked(sink, getters, ordinal)); + result = new SqlDateTime(GetDateTime_Unchecked(getters, ordinal)); break; case SqlDbType.SmallInt: - result = new SqlInt16(GetInt16_Unchecked(sink, getters, ordinal)); + result = new SqlInt16(GetInt16_Unchecked(getters, ordinal)); break; case SqlDbType.SmallMoney: - result = GetSqlMoney_Unchecked(sink, getters, ordinal); + result = GetSqlMoney_Unchecked(getters, ordinal); break; case SqlDbType.Text: - result = new SqlString(GetString_Unchecked(sink, getters, ordinal)); + result = new SqlString(GetString_Unchecked(getters, ordinal)); break; case SqlDbType.Timestamp: - result = GetSqlBinary_Unchecked(sink, getters, ordinal); + result = GetSqlBinary_Unchecked(getters, ordinal); break; case SqlDbType.TinyInt: - result = new SqlByte(GetByte_Unchecked(sink, getters, ordinal)); + result = new SqlByte(GetByte_Unchecked(getters, ordinal)); break; case SqlDbType.VarBinary: - result = GetSqlBinary_Unchecked(sink, getters, ordinal); + result = GetSqlBinary_Unchecked(getters, ordinal); break; case SqlDbType.VarChar: - result = new SqlString(GetString_Unchecked(sink, getters, ordinal)); + result = new SqlString(GetString_Unchecked(getters, ordinal)); break; case SqlDbType.Variant: - metaData = getters.GetVariantType(sink, ordinal); - sink.ProcessMessagesAndThrow(); + metaData = getters.GetVariantType(ordinal); Debug.Assert(SqlDbType.Variant != metaData.SqlDbType, "Variant-within-variant causes endless recursion!"); - result = GetSqlValue(sink, getters, ordinal, metaData, context); + result = GetSqlValue(getters, ordinal, metaData); break; case SqlDbType.Xml: - result = GetSqlXml_Unchecked(sink, getters, ordinal, context); + result = GetSqlXml_Unchecked(getters, ordinal); break; case SqlDbType.Udt: - result = GetUdt_LengthChecked(sink, getters, ordinal, metaData); + result = GetUdt_LengthChecked(getters, ordinal, metaData); break; } } @@ -1249,24 +1206,24 @@ internal static object NullUdtInstance(SmiMetaData metaData) // Strongly-typed setters are a bit simpler than their corresponding getters. // 1) check to make sure the type is compatible (exception if not) // 2) push the data - internal static void SetDBNull(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal) + internal static void SetDBNull(ITypedSettersV3 setters, int ordinal) { - SetDBNull_Unchecked(sink, setters, ordinal); + SetDBNull_Unchecked(setters, ordinal); } - internal static void SetBoolean(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, bool value) + internal static void SetBoolean(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, bool value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.Boolean); - SetBoolean_Unchecked(sink, setters, ordinal, value); + SetBoolean_Unchecked(setters, ordinal, value); } - internal static void SetByte(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, byte value) + internal static void SetByte(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, byte value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.Byte); - SetByte_Unchecked(sink, setters, ordinal, value); + SetByte_Unchecked(setters, ordinal, value); } - internal static long SetBytes(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, long fieldOffset, byte[] buffer, int bufferOffset, int length) + internal static long SetBytes(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, long fieldOffset, byte[] buffer, int bufferOffset, int length) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.ByteArray); if (buffer == null) @@ -1284,10 +1241,10 @@ internal static long SetBytes(SmiEventSink_Default sink, ITypedSettersV3 setters fieldOffset = 0; bufferOffset = 0; } - return SetBytes_Unchecked(sink, setters, ordinal, fieldOffset, buffer, bufferOffset, length); + return SetBytes_Unchecked(setters, ordinal, fieldOffset, buffer, bufferOffset, length); } - internal static long SetBytesLength(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, long length) + internal static long SetBytesLength(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, long length) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.ByteArray); @@ -1301,13 +1258,12 @@ internal static long SetBytesLength(SmiEventSink_Default sink, ITypedSettersV3 s length = metaData.MaxLength; } - setters.SetBytesLength(sink, ordinal, length); - sink.ProcessMessagesAndThrow(); + setters.SetBytesLength(ordinal, length); return length; } - internal static long SetChars(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, long fieldOffset, char[] buffer, int bufferOffset, int length) + internal static long SetChars(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, long fieldOffset, char[] buffer, int bufferOffset, int length) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.CharArray); if (buffer == null) @@ -1325,184 +1281,183 @@ internal static long SetChars(SmiEventSink_Default sink, ITypedSettersV3 setters fieldOffset = 0; bufferOffset = 0; } - return SetChars_Unchecked(sink, setters, ordinal, fieldOffset, buffer, bufferOffset, length); + return SetChars_Unchecked(setters, ordinal, fieldOffset, buffer, bufferOffset, length); } - internal static void SetDateTime(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, DateTime value) + internal static void SetDateTime(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, DateTime value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.DateTime); - SetDateTime_Checked(sink, setters, ordinal, metaData, value); + SetDateTime_Checked(setters, ordinal, metaData, value); } - internal static void SetDateTimeOffset(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, DateTimeOffset value, bool settersSupport2008DateTime = true) + internal static void SetDateTimeOffset(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, DateTimeOffset value, bool settersSupport2008DateTime = true) { if (!settersSupport2008DateTime) { throw ADP.InvalidCast(); } ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.DateTimeOffset); - SetDateTimeOffset_Unchecked(sink, (SmiTypedGetterSetter)setters, ordinal, value); + SetDateTimeOffset_Unchecked((SmiTypedGetterSetter)setters, ordinal, value); } - internal static void SetDecimal(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, decimal value) + internal static void SetDecimal(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, decimal value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.Decimal); - SetDecimal_PossiblyMoney(sink, setters, ordinal, metaData, value); + SetDecimal_PossiblyMoney(setters, ordinal, metaData, value); } - internal static void SetDouble(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, double value) + internal static void SetDouble(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, double value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.Double); - SetDouble_Unchecked(sink, setters, ordinal, value); + SetDouble_Unchecked(setters, ordinal, value); } - internal static void SetGuid(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, Guid value) + internal static void SetGuid(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, Guid value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.Guid); - SetGuid_Unchecked(sink, setters, ordinal, value); + SetGuid_Unchecked(setters, ordinal, value); } - internal static void SetInt16(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, short value) + internal static void SetInt16(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, short value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.Int16); - SetInt16_Unchecked(sink, setters, ordinal, value); + SetInt16_Unchecked(setters, ordinal, value); } - internal static void SetInt32(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, int value) + internal static void SetInt32(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, int value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.Int32); - SetInt32_Unchecked(sink, setters, ordinal, value); + SetInt32_Unchecked(setters, ordinal, value); } - internal static void SetInt64(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, long value) + internal static void SetInt64(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, long value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.Int64); - SetInt64_Unchecked(sink, setters, ordinal, value); + SetInt64_Unchecked(setters, ordinal, value); } - internal static void SetSingle(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, float value) + internal static void SetSingle(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, float value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.Single); - SetSingle_Unchecked(sink, setters, ordinal, value); + SetSingle_Unchecked(setters, ordinal, value); } - internal static void SetSqlBinary(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlBinary value) + internal static void SetSqlBinary(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlBinary value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.SqlBinary); - SetSqlBinary_LengthChecked(sink, setters, ordinal, metaData, value, 0); + SetSqlBinary_LengthChecked(setters, ordinal, metaData, value, 0); } - internal static void SetSqlBoolean(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlBoolean value) + internal static void SetSqlBoolean(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlBoolean value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.SqlBoolean); - SetSqlBoolean_Unchecked(sink, setters, ordinal, value); + SetSqlBoolean_Unchecked(setters, ordinal, value); } - internal static void SetSqlByte(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlByte value) + internal static void SetSqlByte(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlByte value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.SqlByte); - SetSqlByte_Unchecked(sink, setters, ordinal, value); + SetSqlByte_Unchecked(setters, ordinal, value); } - internal static void SetSqlBytes(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlBytes value) + internal static void SetSqlBytes(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlBytes value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.SqlBytes); - SetSqlBytes_LengthChecked(sink, setters, ordinal, metaData, value, 0); + SetSqlBytes_LengthChecked(setters, ordinal, metaData, value, 0); } - internal static void SetSqlChars(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlChars value) + internal static void SetSqlChars(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlChars value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.SqlChars); - SetSqlChars_LengthChecked(sink, setters, ordinal, metaData, value, 0); + SetSqlChars_LengthChecked(setters, ordinal, metaData, value, 0); } - internal static void SetSqlDateTime(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlDateTime value) + internal static void SetSqlDateTime(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlDateTime value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.SqlDateTime); - SetSqlDateTime_Checked(sink, setters, ordinal, metaData, value); + SetSqlDateTime_Checked(setters, ordinal, metaData, value); } - internal static void SetSqlDecimal(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlDecimal value) + internal static void SetSqlDecimal(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlDecimal value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.SqlDecimal); - SetSqlDecimal_Unchecked(sink, setters, ordinal, value); + SetSqlDecimal_Unchecked(setters, ordinal, value); } - internal static void SetSqlDouble(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlDouble value) + internal static void SetSqlDouble(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlDouble value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.SqlDouble); - SetSqlDouble_Unchecked(sink, setters, ordinal, value); + SetSqlDouble_Unchecked(setters, ordinal, value); } - internal static void SetSqlGuid(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlGuid value) + internal static void SetSqlGuid(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlGuid value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.SqlGuid); - SetSqlGuid_Unchecked(sink, setters, ordinal, value); + SetSqlGuid_Unchecked(setters, ordinal, value); } - internal static void SetSqlInt16(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlInt16 value) + internal static void SetSqlInt16(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlInt16 value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.SqlInt16); - SetSqlInt16_Unchecked(sink, setters, ordinal, value); + SetSqlInt16_Unchecked(setters, ordinal, value); } - internal static void SetSqlInt32(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlInt32 value) + internal static void SetSqlInt32(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlInt32 value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.SqlInt32); - SetSqlInt32_Unchecked(sink, setters, ordinal, value); + SetSqlInt32_Unchecked(setters, ordinal, value); } - internal static void SetSqlInt64(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlInt64 value) + internal static void SetSqlInt64(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlInt64 value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.SqlInt64); - SetSqlInt64_Unchecked(sink, setters, ordinal, value); + SetSqlInt64_Unchecked(setters, ordinal, value); } - internal static void SetSqlMoney(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlMoney value) + internal static void SetSqlMoney(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlMoney value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.SqlMoney); - SetSqlMoney_Checked(sink, setters, ordinal, metaData, value); + SetSqlMoney_Checked(setters, ordinal, metaData, value); } - internal static void SetSqlSingle(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlSingle value) + internal static void SetSqlSingle(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlSingle value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.SqlSingle); - SetSqlSingle_Unchecked(sink, setters, ordinal, value); + SetSqlSingle_Unchecked(setters, ordinal, value); } - internal static void SetSqlString(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlString value) + internal static void SetSqlString(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlString value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.SqlString); - SetSqlString_LengthChecked(sink, setters, ordinal, metaData, value, 0); + SetSqlString_LengthChecked(setters, ordinal, metaData, value, 0); } - internal static void SetSqlXml(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlXml value) + internal static void SetSqlXml(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlXml value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.SqlXml); - SetSqlXml_Unchecked(sink, setters, ordinal, value); + SetSqlXml_Unchecked(setters, ordinal, value); } - internal static void SetString(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, string value) + internal static void SetString(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, string value) { ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.String); - SetString_LengthChecked(sink, setters, ordinal, metaData, value, 0); + SetString_LengthChecked(setters, ordinal, metaData, value, 0); } - internal static void SetTimeSpan(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, TimeSpan value, bool settersSupport2008DateTime = true) + internal static void SetTimeSpan(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, TimeSpan value, bool settersSupport2008DateTime = true) { if (!settersSupport2008DateTime) { throw ADP.InvalidCast(); } ThrowIfInvalidSetterAccess(metaData, ExtendedClrTypeCode.TimeSpan); - SetTimeSpan_Checked(sink, (SmiTypedGetterSetter)setters, ordinal, metaData, value); + SetTimeSpan_Checked((SmiTypedGetterSetter)setters, ordinal, metaData, value); } // Implements SqlClient 2.0-compatible SetValue() semantics // Assumes caller already validated type against the metadata, other than trimming lengths internal static void SetCompatibleValue( - SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, // metadata for target setter column @@ -1530,57 +1485,57 @@ value is DataFeed case ExtendedClrTypeCode.Invalid: throw ADP.UnknownDataType(value.GetType()); case ExtendedClrTypeCode.Boolean: - SetBoolean_Unchecked(sink, setters, ordinal, (bool)value); + SetBoolean_Unchecked(setters, ordinal, (bool)value); break; case ExtendedClrTypeCode.Byte: - SetByte_Unchecked(sink, setters, ordinal, (byte)value); + SetByte_Unchecked(setters, ordinal, (byte)value); break; case ExtendedClrTypeCode.Char: { char[] charsValue = new char[] { (char)value }; // recur with array type - SetCompatibleValue(sink, setters, ordinal, metaData, charsValue, ExtendedClrTypeCode.CharArray, 0); + SetCompatibleValue(setters, ordinal, metaData, charsValue, ExtendedClrTypeCode.CharArray, 0); break; } #if NET case ExtendedClrTypeCode.DateOnly: - SetDateTime_Checked(sink, setters, ordinal, metaData, ((DateOnly)value).ToDateTime(new TimeOnly(0, 0))); + SetDateTime_Checked(setters, ordinal, metaData, ((DateOnly)value).ToDateTime(new TimeOnly(0, 0))); break; case ExtendedClrTypeCode.TimeOnly: - SetTimeSpan_Checked(sink, (SmiTypedGetterSetter)setters, ordinal, metaData, ((TimeOnly)value).ToTimeSpan()); + SetTimeSpan_Checked((SmiTypedGetterSetter)setters, ordinal, metaData, ((TimeOnly)value).ToTimeSpan()); break; #endif case ExtendedClrTypeCode.DateTime: - SetDateTime_Checked(sink, setters, ordinal, metaData, (DateTime)value); + SetDateTime_Checked(setters, ordinal, metaData, (DateTime)value); break; case ExtendedClrTypeCode.DBNull: - SetDBNull_Unchecked(sink, setters, ordinal); + SetDBNull_Unchecked(setters, ordinal); break; case ExtendedClrTypeCode.Decimal: - SetDecimal_PossiblyMoney(sink, setters, ordinal, metaData, (decimal)value); + SetDecimal_PossiblyMoney(setters, ordinal, metaData, (decimal)value); break; case ExtendedClrTypeCode.Double: - SetDouble_Unchecked(sink, setters, ordinal, (double)value); + SetDouble_Unchecked(setters, ordinal, (double)value); break; case ExtendedClrTypeCode.Empty: - SetDBNull_Unchecked(sink, setters, ordinal); + SetDBNull_Unchecked(setters, ordinal); break; case ExtendedClrTypeCode.Int16: - SetInt16_Unchecked(sink, setters, ordinal, (short)value); + SetInt16_Unchecked(setters, ordinal, (short)value); break; case ExtendedClrTypeCode.Int32: - SetInt32_Unchecked(sink, setters, ordinal, (int)value); + SetInt32_Unchecked(setters, ordinal, (int)value); break; case ExtendedClrTypeCode.Int64: - SetInt64_Unchecked(sink, setters, ordinal, (long)value); + SetInt64_Unchecked(setters, ordinal, (long)value); break; case ExtendedClrTypeCode.SByte: throw ADP.InvalidCast(); case ExtendedClrTypeCode.Single: - SetSingle_Unchecked(sink, setters, ordinal, (float)value); + SetSingle_Unchecked(setters, ordinal, (float)value); break; case ExtendedClrTypeCode.String: - SetString_LengthChecked(sink, setters, ordinal, metaData, (string)value, offset); + SetString_LengthChecked(setters, ordinal, metaData, (string)value, offset); break; case ExtendedClrTypeCode.UInt16: throw ADP.InvalidCast(); @@ -1589,73 +1544,73 @@ value is DataFeed case ExtendedClrTypeCode.UInt64: throw ADP.InvalidCast(); case ExtendedClrTypeCode.Object: - SetUdt_LengthChecked(sink, setters, ordinal, metaData, value); + SetUdt_LengthChecked(setters, ordinal, metaData, value); break; case ExtendedClrTypeCode.ByteArray: - SetByteArray_LengthChecked(sink, setters, ordinal, metaData, (byte[])value, offset); + SetByteArray_LengthChecked(setters, ordinal, metaData, (byte[])value, offset); break; case ExtendedClrTypeCode.CharArray: - SetCharArray_LengthChecked(sink, setters, ordinal, metaData, (char[])value, offset); + SetCharArray_LengthChecked(setters, ordinal, metaData, (char[])value, offset); break; case ExtendedClrTypeCode.Guid: - SetGuid_Unchecked(sink, setters, ordinal, (Guid)value); + SetGuid_Unchecked(setters, ordinal, (Guid)value); break; case ExtendedClrTypeCode.SqlBinary: - SetSqlBinary_LengthChecked(sink, setters, ordinal, metaData, (SqlBinary)value, offset); + SetSqlBinary_LengthChecked(setters, ordinal, metaData, (SqlBinary)value, offset); break; case ExtendedClrTypeCode.SqlBoolean: - SetSqlBoolean_Unchecked(sink, setters, ordinal, (SqlBoolean)value); + SetSqlBoolean_Unchecked(setters, ordinal, (SqlBoolean)value); break; case ExtendedClrTypeCode.SqlByte: - SetSqlByte_Unchecked(sink, setters, ordinal, (SqlByte)value); + SetSqlByte_Unchecked(setters, ordinal, (SqlByte)value); break; case ExtendedClrTypeCode.SqlDateTime: - SetSqlDateTime_Checked(sink, setters, ordinal, metaData, (SqlDateTime)value); + SetSqlDateTime_Checked(setters, ordinal, metaData, (SqlDateTime)value); break; case ExtendedClrTypeCode.SqlDouble: - SetSqlDouble_Unchecked(sink, setters, ordinal, (SqlDouble)value); + SetSqlDouble_Unchecked(setters, ordinal, (SqlDouble)value); break; case ExtendedClrTypeCode.SqlGuid: - SetSqlGuid_Unchecked(sink, setters, ordinal, (SqlGuid)value); + SetSqlGuid_Unchecked(setters, ordinal, (SqlGuid)value); break; case ExtendedClrTypeCode.SqlInt16: - SetSqlInt16_Unchecked(sink, setters, ordinal, (SqlInt16)value); + SetSqlInt16_Unchecked(setters, ordinal, (SqlInt16)value); break; case ExtendedClrTypeCode.SqlInt32: - SetSqlInt32_Unchecked(sink, setters, ordinal, (SqlInt32)value); + SetSqlInt32_Unchecked(setters, ordinal, (SqlInt32)value); break; case ExtendedClrTypeCode.SqlInt64: - SetSqlInt64_Unchecked(sink, setters, ordinal, (SqlInt64)value); + SetSqlInt64_Unchecked(setters, ordinal, (SqlInt64)value); break; case ExtendedClrTypeCode.SqlMoney: - SetSqlMoney_Checked(sink, setters, ordinal, metaData, (SqlMoney)value); + SetSqlMoney_Checked(setters, ordinal, metaData, (SqlMoney)value); break; case ExtendedClrTypeCode.SqlDecimal: - SetSqlDecimal_Unchecked(sink, setters, ordinal, (SqlDecimal)value); + SetSqlDecimal_Unchecked(setters, ordinal, (SqlDecimal)value); break; case ExtendedClrTypeCode.SqlSingle: - SetSqlSingle_Unchecked(sink, setters, ordinal, (SqlSingle)value); + SetSqlSingle_Unchecked(setters, ordinal, (SqlSingle)value); break; case ExtendedClrTypeCode.SqlString: - SetSqlString_LengthChecked(sink, setters, ordinal, metaData, (SqlString)value, offset); + SetSqlString_LengthChecked(setters, ordinal, metaData, (SqlString)value, offset); break; case ExtendedClrTypeCode.SqlChars: - SetSqlChars_LengthChecked(sink, setters, ordinal, metaData, (SqlChars)value, offset); + SetSqlChars_LengthChecked(setters, ordinal, metaData, (SqlChars)value, offset); break; case ExtendedClrTypeCode.SqlBytes: - SetSqlBytes_LengthChecked(sink, setters, ordinal, metaData, (SqlBytes)value, offset); + SetSqlBytes_LengthChecked(setters, ordinal, metaData, (SqlBytes)value, offset); break; case ExtendedClrTypeCode.SqlXml: - SetSqlXml_Unchecked(sink, setters, ordinal, (SqlXml)value); + SetSqlXml_Unchecked(setters, ordinal, (SqlXml)value); break; case ExtendedClrTypeCode.Stream: - SetStream_Unchecked(sink, setters, ordinal, metaData, (StreamDataFeed)value); + SetStream_Unchecked(setters, ordinal, metaData, (StreamDataFeed)value); break; case ExtendedClrTypeCode.TextReader: - SetTextReader_Unchecked(sink, setters, ordinal, metaData, (TextDataFeed)value); + SetTextReader_Unchecked(setters, ordinal, metaData, (TextDataFeed)value); break; case ExtendedClrTypeCode.XmlReader: - SetXmlReader_Unchecked(sink, setters, ordinal, ((XmlDataFeed)value)._source); + SetXmlReader_Unchecked(setters, ordinal, ((XmlDataFeed)value)._source); break; default: Debug.Fail("Unvalidated extendedtypecode: " + typeCode); @@ -1666,7 +1621,6 @@ value is DataFeed // VSTFDevDiv#479681 - Data corruption when sending 2008 Date types to the server via TVP // Ensures proper handling on DateTime2 sub type for Sql_Variants and TVPs. internal static void SetCompatibleValueV200( - SmiEventSink_Default sink, SmiTypedGetterSetter setters, int ordinal, SmiMetaData metaData, @@ -1691,15 +1645,15 @@ SqlBuffer.StorageType storageType if (typeCode == ExtendedClrTypeCode.DateTime) { if (storageType == SqlBuffer.StorageType.DateTime2) - SetDateTime2_Checked(sink, setters, ordinal, metaData, (DateTime)value); + SetDateTime2_Checked(setters, ordinal, metaData, (DateTime)value); else if (storageType == SqlBuffer.StorageType.Date) - SetDate_Checked(sink, setters, ordinal, metaData, (DateTime)value); + SetDate_Checked(setters, ordinal, metaData, (DateTime)value); else - SetDateTime_Checked(sink, setters, ordinal, metaData, (DateTime)value); + SetDateTime_Checked(setters, ordinal, metaData, (DateTime)value); } else { - SetCompatibleValueV200(sink, setters, ordinal, metaData, value, typeCode, offset, peekAhead); + SetCompatibleValueV200(setters, ordinal, metaData, value, typeCode, offset, peekAhead); } } @@ -1707,7 +1661,6 @@ SqlBuffer.StorageType storageType // Assumes caller already validated basic type against the metadata, other than trimming lengths and // checking individual field values (TVPs) internal static void SetCompatibleValueV200( - SmiEventSink_Default sink, SmiTypedGetterSetter setters, int ordinal, SmiMetaData metaData, @@ -1731,164 +1684,37 @@ ParameterPeekAheadValue peekAhead switch (typeCode) { case ExtendedClrTypeCode.DataTable: - SetDataTable_Unchecked(sink, setters, ordinal, metaData, (DataTable)value); + SetDataTable_Unchecked(setters, ordinal, metaData, (DataTable)value); break; case ExtendedClrTypeCode.DbDataReader: - SetDbDataReader_Unchecked(sink, setters, ordinal, metaData, (DbDataReader)value); + SetDbDataReader_Unchecked(setters, ordinal, metaData, (DbDataReader)value); break; case ExtendedClrTypeCode.IEnumerableOfSqlDataRecord: - SetIEnumerableOfSqlDataRecord_Unchecked(sink, setters, ordinal, metaData, (IEnumerable)value, peekAhead); + SetIEnumerableOfSqlDataRecord_Unchecked(setters, ordinal, metaData, (IEnumerable)value, peekAhead); break; case ExtendedClrTypeCode.TimeSpan: - SetTimeSpan_Checked(sink, setters, ordinal, metaData, (TimeSpan)value); + SetTimeSpan_Checked(setters, ordinal, metaData, (TimeSpan)value); break; case ExtendedClrTypeCode.DateTimeOffset: - SetDateTimeOffset_Unchecked(sink, setters, ordinal, (DateTimeOffset)value); + SetDateTimeOffset_Unchecked(setters, ordinal, (DateTimeOffset)value); break; default: - SetCompatibleValue(sink, setters, ordinal, metaData, value, typeCode, offset); + SetCompatibleValue(setters, ordinal, metaData, value, typeCode, offset); break; } } - // Copy multiple fields from reader to ITypedSettersV3 - // Assumes caller enforces that reader and setter metadata are compatible - internal static void FillCompatibleITypedSettersFromReader(SmiEventSink_Default sink, ITypedSettersV3 setters, SmiMetaData[] metaData, SqlDataReader reader) - { - for (int i = 0; i < metaData.Length; i++) - { - if (reader.IsDBNull(i)) - { - ValueUtilsSmi.SetDBNull_Unchecked(sink, setters, i); - } - else - { - switch (metaData[i].SqlDbType) - { - case SqlDbType.BigInt: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Int64)); - ValueUtilsSmi.SetInt64_Unchecked(sink, setters, i, reader.GetInt64(i)); - break; - case SqlDbType.Binary: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlBytes)); - ValueUtilsSmi.SetSqlBytes_LengthChecked(sink, setters, i, metaData[i], reader.GetSqlBytes(i), 0); - break; - case SqlDbType.Bit: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Boolean)); - SetBoolean_Unchecked(sink, setters, i, reader.GetBoolean(i)); - break; - case SqlDbType.Char: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlChars)); - SetSqlChars_LengthChecked(sink, setters, i, metaData[i], reader.GetSqlChars(i), 0); - break; - case SqlDbType.DateTime: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.DateTime)); - SetDateTime_Checked(sink, setters, i, metaData[i], reader.GetDateTime(i)); - break; - case SqlDbType.Decimal: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlDecimal)); - SetSqlDecimal_Unchecked(sink, setters, i, reader.GetSqlDecimal(i)); - break; - case SqlDbType.Float: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Double)); - SetDouble_Unchecked(sink, setters, i, reader.GetDouble(i)); - break; - case SqlDbType.Image: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlBytes)); - SetSqlBytes_LengthChecked(sink, setters, i, metaData[i], reader.GetSqlBytes(i), 0); - break; - case SqlDbType.Int: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Int32)); - SetInt32_Unchecked(sink, setters, i, reader.GetInt32(i)); - break; - case SqlDbType.Money: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlMoney)); - SetSqlMoney_Unchecked(sink, setters, i, metaData[i], reader.GetSqlMoney(i)); - break; - case SqlDbType.NChar: - case SqlDbType.NText: - case SqlDbType.NVarChar: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlChars)); - SetSqlChars_LengthChecked(sink, setters, i, metaData[i], reader.GetSqlChars(i), 0); - break; - case SqlDbType.Real: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Single)); - SetSingle_Unchecked(sink, setters, i, reader.GetFloat(i)); - break; - case SqlDbType.UniqueIdentifier: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Guid)); - SetGuid_Unchecked(sink, setters, i, reader.GetGuid(i)); - break; - case SqlDbType.SmallDateTime: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.DateTime)); - SetDateTime_Checked(sink, setters, i, metaData[i], reader.GetDateTime(i)); - break; - case SqlDbType.SmallInt: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Int16)); - SetInt16_Unchecked(sink, setters, i, reader.GetInt16(i)); - break; - case SqlDbType.SmallMoney: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlMoney)); - SetSqlMoney_Checked(sink, setters, i, metaData[i], reader.GetSqlMoney(i)); - break; - case SqlDbType.Text: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlChars)); - SetSqlChars_LengthChecked(sink, setters, i, metaData[i], reader.GetSqlChars(i), 0); - break; - case SqlDbType.Timestamp: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlBytes)); - SetSqlBytes_LengthChecked(sink, setters, i, metaData[i], reader.GetSqlBytes(i), 0); - break; - case SqlDbType.TinyInt: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Byte)); - SetByte_Unchecked(sink, setters, i, reader.GetByte(i)); - break; - case SqlDbType.VarBinary: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlBytes)); - SetSqlBytes_LengthChecked(sink, setters, i, metaData[i], reader.GetSqlBytes(i), 0); - break; - case SqlDbType.VarChar: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.String)); - SetSqlChars_LengthChecked(sink, setters, i, metaData[i], reader.GetSqlChars(i), 0); - break; - case SqlDbType.Xml: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlXml)); - SetSqlXml_Unchecked(sink, setters, i, reader.GetSqlXml(i)); - break; - case SqlDbType.Variant: - object o = reader.GetSqlValue(i); - ExtendedClrTypeCode typeCode = MetaDataUtilsSmi.DetermineExtendedTypeCode(o); - SetCompatibleValue(sink, setters, i, metaData[i], o, typeCode, 0); - break; - - case SqlDbType.Udt: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlBytes)); - SetSqlBytes_LengthChecked(sink, setters, i, metaData[i], reader.GetSqlBytes(i), 0); - break; - - default: - // In order for us to get here we would have to have an - // invalid instance of SqlDbType, or one would have to add - // new member to SqlDbType without adding a case in this - // switch, hence the assert. - Debug.Fail("unsupported DbType:" + metaData[i].SqlDbType.ToString()); - throw ADP.NotSupported(); - } - } - } - } - // Copy multiple fields from reader to SmiTypedGetterSetter // Supports V200 code path, without damaging backward compat for V100 code. // Main differences are supporting DbDataReader, and for binary, character, decimal and Udt types. // Assumes caller enforces that reader and setter metadata are compatible - internal static void FillCompatibleSettersFromReader(SmiEventSink_Default sink, SmiTypedGetterSetter setters, IList metaData, DbDataReader reader) + internal static void FillCompatibleSettersFromReader(SmiTypedGetterSetter setters, IList metaData, DbDataReader reader) { for (int i = 0; i < metaData.Count; i++) { if (reader.IsDBNull(i)) { - ValueUtilsSmi.SetDBNull_Unchecked(sink, setters, i); + ValueUtilsSmi.SetDBNull_Unchecked(setters, i); } else { @@ -1896,23 +1722,23 @@ internal static void FillCompatibleSettersFromReader(SmiEventSink_Default sink, { case SqlDbType.BigInt: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Int64)); - SetInt64_Unchecked(sink, setters, i, reader.GetInt64(i)); + SetInt64_Unchecked(setters, i, reader.GetInt64(i)); break; case SqlDbType.Binary: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.ByteArray)); - SetBytes_FromReader(sink, setters, i, metaData[i], reader, 0); + SetBytes_FromReader(setters, i, metaData[i], reader, 0); break; case SqlDbType.Bit: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Boolean)); - SetBoolean_Unchecked(sink, setters, i, reader.GetBoolean(i)); + SetBoolean_Unchecked(setters, i, reader.GetBoolean(i)); break; case SqlDbType.Char: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.CharArray)); - SetCharsOrString_FromReader(sink, setters, i, metaData[i], reader, 0); + SetCharsOrString_FromReader(setters, i, metaData[i], reader, 0); break; case SqlDbType.DateTime: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.DateTime)); - SetDateTime_Checked(sink, setters, i, metaData[i], reader.GetDateTime(i)); + SetDateTime_Checked(setters, i, metaData[i], reader.GetDateTime(i)); break; case SqlDbType.Decimal: { // block to scope sqlReader local to avoid conflicts @@ -1920,86 +1746,86 @@ internal static void FillCompatibleSettersFromReader(SmiEventSink_Default sink, // Support full fidelity for SqlDataReader if (reader is SqlDataReader sqlReader) { - SetSqlDecimal_Unchecked(sink, setters, i, sqlReader.GetSqlDecimal(i)); + SetSqlDecimal_Unchecked(setters, i, sqlReader.GetSqlDecimal(i)); } else { - SetSqlDecimal_Unchecked(sink, setters, i, new SqlDecimal(reader.GetDecimal(i))); + SetSqlDecimal_Unchecked(setters, i, new SqlDecimal(reader.GetDecimal(i))); } } break; case SqlDbType.Float: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Double)); - SetDouble_Unchecked(sink, setters, i, reader.GetDouble(i)); + SetDouble_Unchecked(setters, i, reader.GetDouble(i)); break; case SqlDbType.Image: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.ByteArray)); - SetBytes_FromReader(sink, setters, i, metaData[i], reader, 0); + SetBytes_FromReader(setters, i, metaData[i], reader, 0); break; case SqlDbType.Int: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Int32)); - SetInt32_Unchecked(sink, setters, i, reader.GetInt32(i)); + SetInt32_Unchecked(setters, i, reader.GetInt32(i)); break; case SqlDbType.Money: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlMoney)); - SetSqlMoney_Checked(sink, setters, i, metaData[i], new SqlMoney(reader.GetDecimal(i))); + SetSqlMoney_Checked(setters, i, metaData[i], new SqlMoney(reader.GetDecimal(i))); break; case SqlDbType.NChar: case SqlDbType.NText: case SqlDbType.NVarChar: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.CharArray)); - SetCharsOrString_FromReader(sink, setters, i, metaData[i], reader, 0); + SetCharsOrString_FromReader(setters, i, metaData[i], reader, 0); break; case SqlDbType.Real: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Single)); - SetSingle_Unchecked(sink, setters, i, reader.GetFloat(i)); + SetSingle_Unchecked(setters, i, reader.GetFloat(i)); break; case SqlDbType.UniqueIdentifier: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Guid)); - SetGuid_Unchecked(sink, setters, i, reader.GetGuid(i)); + SetGuid_Unchecked(setters, i, reader.GetGuid(i)); break; case SqlDbType.SmallDateTime: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.DateTime)); - SetDateTime_Checked(sink, setters, i, metaData[i], reader.GetDateTime(i)); + SetDateTime_Checked(setters, i, metaData[i], reader.GetDateTime(i)); break; case SqlDbType.SmallInt: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Int16)); - SetInt16_Unchecked(sink, setters, i, reader.GetInt16(i)); + SetInt16_Unchecked(setters, i, reader.GetInt16(i)); break; case SqlDbType.SmallMoney: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlMoney)); - SetSqlMoney_Checked(sink, setters, i, metaData[i], new SqlMoney(reader.GetDecimal(i))); + SetSqlMoney_Checked(setters, i, metaData[i], new SqlMoney(reader.GetDecimal(i))); break; case SqlDbType.Text: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.CharArray)); - SetCharsOrString_FromReader(sink, setters, i, metaData[i], reader, 0); + SetCharsOrString_FromReader(setters, i, metaData[i], reader, 0); break; case SqlDbType.Timestamp: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.ByteArray)); - SetBytes_FromReader(sink, setters, i, metaData[i], reader, 0); + SetBytes_FromReader(setters, i, metaData[i], reader, 0); break; case SqlDbType.TinyInt: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Byte)); - SetByte_Unchecked(sink, setters, i, reader.GetByte(i)); + SetByte_Unchecked(setters, i, reader.GetByte(i)); break; case SqlDbType.VarBinary: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.ByteArray)); - SetBytes_FromReader(sink, setters, i, metaData[i], reader, 0); + SetBytes_FromReader(setters, i, metaData[i], reader, 0); break; case SqlDbType.VarChar: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.String)); - SetCharsOrString_FromReader(sink, setters, i, metaData[i], reader, 0); + SetCharsOrString_FromReader(setters, i, metaData[i], reader, 0); break; case SqlDbType.Xml: { Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlXml)); if (reader is SqlDataReader sqlReader) { - SetSqlXml_Unchecked(sink, setters, i, sqlReader.GetSqlXml(i)); + SetSqlXml_Unchecked(setters, i, sqlReader.GetSqlXml(i)); } else { - SetBytes_FromReader(sink, setters, i, metaData[i], reader, 0); + SetBytes_FromReader(setters, i, metaData[i], reader, 0); } } break; @@ -2017,20 +1843,18 @@ internal static void FillCompatibleSettersFromReader(SmiEventSink_Default sink, { o = reader.GetValue(i); } - ExtendedClrTypeCode typeCode = MetaDataUtilsSmi.DetermineExtendedTypeCodeForUseWithSqlDbType(metaData[i].SqlDbType, metaData[i].IsMultiValued, o, null -#if NETFRAMEWORK - ,// TODO: this version works for shipping VS2008, since only 2008 (TVP) codepath calls this method at this time. - // Need a better story for smi versioning of ValueUtilsSmi post-VS2008 - SmiContextFactory.Sql2008Version -#endif - ); + ExtendedClrTypeCode typeCode = MetaDataUtilsSmi.DetermineExtendedTypeCodeForUseWithSqlDbType( + metaData[i].SqlDbType, + metaData[i].IsMultiValued, + value: o, + udtType: null); if ((storageType == SqlBuffer.StorageType.DateTime2) || (storageType == SqlBuffer.StorageType.Date)) { - SetCompatibleValueV200(sink, setters, i, metaData[i], o, typeCode, 0, null, storageType); + SetCompatibleValueV200(setters, i, metaData[i], o, typeCode, 0, null, storageType); } else { - SetCompatibleValueV200(sink, setters, i, metaData[i], o, typeCode, 0, null); + SetCompatibleValueV200(setters, i, metaData[i], o, typeCode, 0, null); } } break; @@ -2038,7 +1862,7 @@ internal static void FillCompatibleSettersFromReader(SmiEventSink_Default sink, case SqlDbType.Udt: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.ByteArray)); // Skip serialization for Udt types. - SetBytes_FromReader(sink, setters, i, metaData[i], reader, 0); + SetBytes_FromReader(setters, i, metaData[i], reader, 0); break; // SqlDbType.Structured should have been caught before this point for TVPs. SUDTs will still need to implement. @@ -2046,7 +1870,7 @@ internal static void FillCompatibleSettersFromReader(SmiEventSink_Default sink, case SqlDbType.Date: case SqlDbType.DateTime2: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.DateTime)); - SetDateTime_Checked(sink, setters, i, metaData[i], reader.GetDateTime(i)); + SetDateTime_Checked(setters, i, metaData[i], reader.GetDateTime(i)); break; case SqlDbType.Time: { @@ -2060,7 +1884,7 @@ internal static void FillCompatibleSettersFromReader(SmiEventSink_Default sink, { ts = (TimeSpan)reader.GetValue(i); } - SetTimeSpan_Checked(sink, setters, i, metaData[i], ts); + SetTimeSpan_Checked(setters, i, metaData[i], ts); } break; case SqlDbType.DateTimeOffset: @@ -2075,7 +1899,7 @@ internal static void FillCompatibleSettersFromReader(SmiEventSink_Default sink, { dto = (DateTimeOffset)reader.GetValue(i); } - SetDateTimeOffset_Unchecked(sink, setters, i, dto); + SetDateTimeOffset_Unchecked(setters, i, dto); } break; @@ -2091,7 +1915,7 @@ internal static void FillCompatibleSettersFromReader(SmiEventSink_Default sink, } } - internal static void FillCompatibleSettersFromRecord(SmiEventSink_Default sink, SmiTypedGetterSetter setters, SmiMetaData[] metaData, SqlDataRecord record, SmiDefaultFieldsProperty useDefaultValues) + internal static void FillCompatibleSettersFromRecord(SmiTypedGetterSetter setters, SmiMetaData[] metaData, SqlDataRecord record, SmiDefaultFieldsProperty useDefaultValues) { for (int i = 0; i < metaData.Length; ++i) { @@ -2101,7 +1925,7 @@ internal static void FillCompatibleSettersFromRecord(SmiEventSink_Default sink, } if (record.IsDBNull(i)) { - ValueUtilsSmi.SetDBNull_Unchecked(sink, setters, i); + ValueUtilsSmi.SetDBNull_Unchecked(setters, i); } else { @@ -2109,107 +1933,107 @@ internal static void FillCompatibleSettersFromRecord(SmiEventSink_Default sink, { case SqlDbType.BigInt: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Int64)); - SetInt64_Unchecked(sink, setters, i, record.GetInt64(i)); + SetInt64_Unchecked(setters, i, record.GetInt64(i)); break; case SqlDbType.Binary: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlBytes)); - SetBytes_FromRecord(sink, setters, i, metaData[i], record, 0); + SetBytes_FromRecord(setters, i, metaData[i], record, 0); break; case SqlDbType.Bit: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Boolean)); - SetBoolean_Unchecked(sink, setters, i, record.GetBoolean(i)); + SetBoolean_Unchecked(setters, i, record.GetBoolean(i)); break; case SqlDbType.Char: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlChars)); - SetChars_FromRecord(sink, setters, i, metaData[i], record, 0); + SetChars_FromRecord(setters, i, metaData[i], record, 0); break; case SqlDbType.DateTime: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.DateTime)); - SetDateTime_Checked(sink, setters, i, metaData[i], record.GetDateTime(i)); + SetDateTime_Checked(setters, i, metaData[i], record.GetDateTime(i)); break; case SqlDbType.Decimal: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlDecimal)); - SetSqlDecimal_Unchecked(sink, setters, i, record.GetSqlDecimal(i)); + SetSqlDecimal_Unchecked(setters, i, record.GetSqlDecimal(i)); break; case SqlDbType.Float: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Double)); - SetDouble_Unchecked(sink, setters, i, record.GetDouble(i)); + SetDouble_Unchecked(setters, i, record.GetDouble(i)); break; case SqlDbType.Image: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlBytes)); - SetBytes_FromRecord(sink, setters, i, metaData[i], record, 0); + SetBytes_FromRecord(setters, i, metaData[i], record, 0); break; case SqlDbType.Int: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Int32)); - SetInt32_Unchecked(sink, setters, i, record.GetInt32(i)); + SetInt32_Unchecked(setters, i, record.GetInt32(i)); break; case SqlDbType.Money: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlMoney)); - SetSqlMoney_Unchecked(sink, setters, i, metaData[i], record.GetSqlMoney(i)); + SetSqlMoney_Unchecked(setters, i, metaData[i], record.GetSqlMoney(i)); break; case SqlDbType.NChar: case SqlDbType.NText: case SqlDbType.NVarChar: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlChars)); - SetChars_FromRecord(sink, setters, i, metaData[i], record, 0); + SetChars_FromRecord(setters, i, metaData[i], record, 0); break; case SqlDbType.Real: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Single)); - SetSingle_Unchecked(sink, setters, i, record.GetFloat(i)); + SetSingle_Unchecked(setters, i, record.GetFloat(i)); break; case SqlDbType.UniqueIdentifier: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Guid)); - SetGuid_Unchecked(sink, setters, i, record.GetGuid(i)); + SetGuid_Unchecked(setters, i, record.GetGuid(i)); break; case SqlDbType.SmallDateTime: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.DateTime)); - SetDateTime_Checked(sink, setters, i, metaData[i], record.GetDateTime(i)); + SetDateTime_Checked(setters, i, metaData[i], record.GetDateTime(i)); break; case SqlDbType.SmallInt: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Int16)); - SetInt16_Unchecked(sink, setters, i, record.GetInt16(i)); + SetInt16_Unchecked(setters, i, record.GetInt16(i)); break; case SqlDbType.SmallMoney: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlMoney)); - SetSqlMoney_Checked(sink, setters, i, metaData[i], record.GetSqlMoney(i)); + SetSqlMoney_Checked(setters, i, metaData[i], record.GetSqlMoney(i)); break; case SqlDbType.Text: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlChars)); - SetChars_FromRecord(sink, setters, i, metaData[i], record, 0); + SetChars_FromRecord(setters, i, metaData[i], record, 0); break; case SqlDbType.Timestamp: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlBytes)); - SetBytes_FromRecord(sink, setters, i, metaData[i], record, 0); + SetBytes_FromRecord(setters, i, metaData[i], record, 0); break; case SqlDbType.TinyInt: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Byte)); - SetByte_Unchecked(sink, setters, i, record.GetByte(i)); + SetByte_Unchecked(setters, i, record.GetByte(i)); break; case SqlDbType.VarBinary: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlBytes)); - SetBytes_FromRecord(sink, setters, i, metaData[i], record, 0); + SetBytes_FromRecord(setters, i, metaData[i], record, 0); break; case SqlDbType.VarChar: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.String)); - SetChars_FromRecord(sink, setters, i, metaData[i], record, 0); + SetChars_FromRecord(setters, i, metaData[i], record, 0); break; case SqlDbType.Xml: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlXml)); - SetSqlXml_Unchecked(sink, setters, i, record.GetSqlXml(i)); // perf improvement? + SetSqlXml_Unchecked(setters, i, record.GetSqlXml(i)); // perf improvement? break; case SqlDbType.Variant: object o = record.GetSqlValue(i); ExtendedClrTypeCode typeCode = MetaDataUtilsSmi.DetermineExtendedTypeCode(o); - SetCompatibleValueV200(sink, setters, i, metaData[i], o, typeCode, 0, null /* no peekahead */); + SetCompatibleValueV200(setters, i, metaData[i], o, typeCode, 0, null /* no peekahead */); break; case SqlDbType.Udt: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlBytes)); - SetBytes_FromRecord(sink, setters, i, metaData[i], record, 0); + SetBytes_FromRecord(setters, i, metaData[i], record, 0); break; case SqlDbType.Date: case SqlDbType.DateTime2: Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.DateTime)); - SetDateTime_Checked(sink, setters, i, metaData[i], record.GetDateTime(i)); + SetDateTime_Checked(setters, i, metaData[i], record.GetDateTime(i)); break; case SqlDbType.Time: { @@ -2223,7 +2047,7 @@ internal static void FillCompatibleSettersFromRecord(SmiEventSink_Default sink, { ts = (TimeSpan)record.GetValue(i); } - SetTimeSpan_Checked(sink, setters, i, metaData[i], ts); + SetTimeSpan_Checked(setters, i, metaData[i], ts); } break; case SqlDbType.DateTimeOffset: @@ -2238,7 +2062,7 @@ internal static void FillCompatibleSettersFromRecord(SmiEventSink_Default sink, { dto = (DateTimeOffset)record.GetValue(i); } - SetDateTimeOffset_Unchecked(sink, setters, i, dto); + SetDateTimeOffset_Unchecked(setters, i, dto); } break; @@ -2253,10 +2077,10 @@ internal static void FillCompatibleSettersFromRecord(SmiEventSink_Default sink, // // Common utility code to get lengths correct for trimming // - private static object GetUdt_LengthChecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + private static object GetUdt_LengthChecked(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { object result; - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { Type type = metaData.Type; Debug.Assert(type != null, "Unexpected null of udtType on GetUdt_LengthChecked!"); @@ -2267,35 +2091,35 @@ private static object GetUdt_LengthChecked(SmiEventSink_Default sink, ITypedGett { // Note: do not need to copy getter stream, since it will not be used beyond // deserialization (valid lifetime of getters is limited). - Stream stream = new SmiGettersStream(sink, getters, ordinal, metaData); + Stream stream = new SmiGettersStream(getters, ordinal, metaData); result = SerializationHelperSql9.Deserialize(stream, metaData.Type); } return result; } - private static decimal GetDecimal_PossiblyMoney(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) + private static decimal GetDecimal_PossiblyMoney(ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) { if (metaData.SqlDbType == SqlDbType.Decimal) { - return GetSqlDecimal_Unchecked(sink, getters, ordinal).Value; + return GetSqlDecimal_Unchecked(getters, ordinal).Value; } else { Debug.Assert(metaData.SqlDbType == SqlDbType.Money || metaData.SqlDbType == SqlDbType.SmallMoney, "Unexpected sqldbtype=" + metaData.SqlDbType); - return GetSqlMoney_Unchecked(sink, getters, ordinal).Value; + return GetSqlMoney_Unchecked(getters, ordinal).Value; } } - private static void SetDecimal_PossiblyMoney(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, decimal value) + private static void SetDecimal_PossiblyMoney(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, decimal value) { if (metaData.SqlDbType == SqlDbType.Decimal || metaData.SqlDbType == SqlDbType.Variant) { - SetDecimal_Unchecked(sink, setters, ordinal, value); + SetDecimal_Unchecked(setters, ordinal, value); } else { Debug.Assert(metaData.SqlDbType == SqlDbType.Money || metaData.SqlDbType == SqlDbType.SmallMoney, "Unexpected sqldbtype=" + metaData.SqlDbType); - SetSqlMoney_Checked(sink, setters, ordinal, metaData, new SqlMoney(value)); + SetSqlMoney_Checked(setters, ordinal, metaData, new SqlMoney(value)); } } @@ -2320,40 +2144,40 @@ private static void VerifyTimeRange(SqlDbType dbType, TimeSpan value) } } - private static void SetDateTime_Checked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, DateTime value) + private static void SetDateTime_Checked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, DateTime value) { VerifyDateTimeRange(metaData.SqlDbType, value); - SetDateTime_Unchecked(sink, setters, ordinal, (metaData.SqlDbType == SqlDbType.Date) ? value.Date : value); + SetDateTime_Unchecked(setters, ordinal, (metaData.SqlDbType == SqlDbType.Date) ? value.Date : value); } - private static void SetTimeSpan_Checked(SmiEventSink_Default sink, SmiTypedGetterSetter setters, int ordinal, SmiMetaData metaData, TimeSpan value) + private static void SetTimeSpan_Checked(SmiTypedGetterSetter setters, int ordinal, SmiMetaData metaData, TimeSpan value) { VerifyTimeRange(metaData.SqlDbType, value); - SetTimeSpan_Unchecked(sink, setters, ordinal, value); + SetTimeSpan_Unchecked(setters, ordinal, value); } - private static void SetSqlDateTime_Checked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlDateTime value) + private static void SetSqlDateTime_Checked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlDateTime value) { if (!value.IsNull) { VerifyDateTimeRange(metaData.SqlDbType, value.Value); } - SetSqlDateTime_Unchecked(sink, setters, ordinal, value); + SetSqlDateTime_Unchecked(setters, ordinal, value); } - private static void SetDateTime2_Checked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, DateTime value) + private static void SetDateTime2_Checked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, DateTime value) { VerifyDateTimeRange(metaData.SqlDbType, value); - SetDateTime2_Unchecked(sink, setters, ordinal, metaData, value); + SetDateTime2_Unchecked(setters, ordinal, metaData, value); } - private static void SetDate_Checked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, DateTime value) + private static void SetDate_Checked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, DateTime value) { VerifyDateTimeRange(metaData.SqlDbType, value); - SetDate_Unchecked(sink, setters, ordinal, metaData, value); + SetDate_Unchecked(setters, ordinal, metaData, value); } - private static void SetSqlMoney_Checked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlMoney value) + private static void SetSqlMoney_Checked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlMoney value) { if (!value.IsNull && metaData.SqlDbType == SqlDbType.SmallMoney) { @@ -2363,24 +2187,24 @@ private static void SetSqlMoney_Checked(SmiEventSink_Default sink, ITypedSetters throw SQL.MoneyOverflow(decimalValue.ToString(CultureInfo.InvariantCulture)); } } - SetSqlMoney_Unchecked(sink, setters, ordinal, metaData, value); + SetSqlMoney_Unchecked(setters, ordinal, metaData, value); } - private static void SetByteArray_LengthChecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, byte[] buffer, int offset) + private static void SetByteArray_LengthChecked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, byte[] buffer, int offset) { int length = CheckXetParameters(metaData.SqlDbType, metaData.MaxLength, actualLength: NoLengthLimit, fieldOffset: 0, bufferLength: buffer.Length, bufferOffset: offset, length: buffer.Length - offset); Debug.Assert(length >= 0, "buffer.Length was invalid!"); - SetByteArray_Unchecked(sink, setters, ordinal, buffer, offset, length); + SetByteArray_Unchecked(setters, ordinal, buffer, offset, length); } - private static void SetCharArray_LengthChecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, char[] buffer, int offset) + private static void SetCharArray_LengthChecked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, char[] buffer, int offset) { int length = CheckXetParameters(metaData.SqlDbType, metaData.MaxLength, actualLength: NoLengthLimit, fieldOffset: 0, bufferLength: buffer.Length, bufferOffset: offset, length: buffer.Length - offset); Debug.Assert(length >= 0, "buffer.Length was invalid!"); - SetCharArray_Unchecked(sink, setters, ordinal, buffer, offset, length); + SetCharArray_Unchecked(setters, ordinal, buffer, offset, length); } - private static void SetSqlBinary_LengthChecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlBinary value, int offset) + private static void SetSqlBinary_LengthChecked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlBinary value, int offset) { int length = 0; if (!value.IsNull) @@ -2388,10 +2212,10 @@ private static void SetSqlBinary_LengthChecked(SmiEventSink_Default sink, ITyped length = CheckXetParameters(metaData.SqlDbType, metaData.MaxLength, actualLength: NoLengthLimit, fieldOffset: 0, bufferLength: value.Length, bufferOffset: offset, length: value.Length - offset); Debug.Assert(length >= 0, "value.Length was invalid!"); } - SetSqlBinary_Unchecked(sink, setters, ordinal, value, offset, length); + SetSqlBinary_Unchecked(setters, ordinal, value, offset, length); } - private static void SetBytes_FromRecord(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlDataRecord record, int offset) + private static void SetBytes_FromRecord(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlDataRecord record, int offset) { // Deal with large values by sending bufferLength of NoLengthLimit (== assume // CheckXetParameters will ignore requested-length checks in this case @@ -2424,8 +2248,7 @@ private static void SetBytes_FromRecord(SmiEventSink_Default sink, ITypedSetters bytesWritten != 0 ) { - bytesWritten = setters.SetBytes(sink, ordinal, currentOffset, buffer, 0, checked((int)bytesRead)); - sink.ProcessMessagesAndThrow(); + bytesWritten = setters.SetBytes(ordinal, currentOffset, buffer, 0, checked((int)bytesRead)); checked { currentOffset += bytesWritten; @@ -2434,11 +2257,10 @@ private static void SetBytes_FromRecord(SmiEventSink_Default sink, ITypedSetters } // Make sure to trim any left-over data - setters.SetBytesLength(sink, ordinal, currentOffset); - sink.ProcessMessagesAndThrow(); + setters.SetBytesLength(ordinal, currentOffset); } - private static void SetBytes_FromReader(SmiEventSink_Default sink, SmiTypedGetterSetter setters, int ordinal, SmiMetaData metaData, DbDataReader reader, int offset) + private static void SetBytes_FromReader(SmiTypedGetterSetter setters, int ordinal, SmiMetaData metaData, DbDataReader reader, int offset) { // Deal with large values by sending bufferLength of NoLengthLimit (== assume // CheckXetParameters will ignore requested-length checks in this case) @@ -2459,8 +2281,7 @@ private static void SetBytes_FromReader(SmiEventSink_Default sink, SmiTypedGette bytesWritten != 0 ) { - bytesWritten = setters.SetBytes(sink, ordinal, currentOffset, buffer, 0, checked((int)bytesRead)); - sink.ProcessMessagesAndThrow(); + bytesWritten = setters.SetBytes(ordinal, currentOffset, buffer, 0, checked((int)bytesRead)); checked { currentOffset += bytesWritten; @@ -2469,11 +2290,10 @@ private static void SetBytes_FromReader(SmiEventSink_Default sink, SmiTypedGette } // Make sure to trim any left-over data (remember to trim at end of offset, not just the amount written - setters.SetBytesLength(sink, ordinal, currentOffset); - sink.ProcessMessagesAndThrow(); + setters.SetBytesLength(ordinal, currentOffset); } - private static void SetSqlBytes_LengthChecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlBytes value, int offset) + private static void SetSqlBytes_LengthChecked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlBytes value, int offset) { int length = 0; if (!value.IsNull) @@ -2487,10 +2307,10 @@ private static void SetSqlBytes_LengthChecked(SmiEventSink_Default sink, ITypedS } length = CheckXetParameters(metaData.SqlDbType, metaData.MaxLength, actualLength: NoLengthLimit, fieldOffset: 0, bufferLength: checked((int)bufferLength), bufferOffset: offset, length: checked((int)bufferLength)); } - SetSqlBytes_Unchecked(sink, setters, ordinal, value, 0, length); + SetSqlBytes_Unchecked(setters, ordinal, value, 0, length); } - private static void SetChars_FromRecord(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlDataRecord record, int offset) + private static void SetChars_FromRecord(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlDataRecord record, int offset) { // Deal with large values by sending bufferLength of NoLengthLimit // CheckXetParameters will ignore length checks in this case @@ -2530,8 +2350,7 @@ private static void SetChars_FromRecord(SmiEventSink_Default sink, ITypedSetters charsWritten != 0 ) { - charsWritten = setters.SetChars(sink, ordinal, currentOffset, buffer, 0, checked((int)charsRead)); - sink.ProcessMessagesAndThrow(); + charsWritten = setters.SetChars(ordinal, currentOffset, buffer, 0, checked((int)charsRead)); checked { currentOffset += charsWritten; @@ -2540,19 +2359,18 @@ private static void SetChars_FromRecord(SmiEventSink_Default sink, ITypedSetters } // Make sure to trim any left-over data - setters.SetCharsLength(sink, ordinal, currentOffset); - sink.ProcessMessagesAndThrow(); + setters.SetCharsLength(ordinal, currentOffset); } // Transfer a character value from a reader when we're not sure which GetXXX method the reader will support. // Prefers to chunk data via GetChars, but falls back to GetString if that fails. // Mainly put in place because DataTableReader doesn't support GetChars on string columns, but others could fail too... - private static void SetCharsOrString_FromReader(SmiEventSink_Default sink, SmiTypedGetterSetter setters, int ordinal, SmiMetaData metaData, DbDataReader reader, int offset) + private static void SetCharsOrString_FromReader(SmiTypedGetterSetter setters, int ordinal, SmiMetaData metaData, DbDataReader reader, int offset) { bool success = false; try { - SetChars_FromReader(sink, setters, ordinal, metaData, reader, offset); + SetChars_FromReader(setters, ordinal, metaData, reader, offset); success = true; } catch (Exception e) @@ -2565,12 +2383,12 @@ private static void SetCharsOrString_FromReader(SmiEventSink_Default sink, SmiTy if (!success) { - SetString_FromReader(sink, setters, ordinal, metaData, reader, offset); + SetString_FromReader(setters, ordinal, metaData, reader, offset); } } // Use chunking via SetChars to transfer a value from a reader to a gettersetter - private static void SetChars_FromReader(SmiEventSink_Default sink, SmiTypedGetterSetter setters, int ordinal, SmiMetaData metaData, DbDataReader reader, int offset) + private static void SetChars_FromReader(SmiTypedGetterSetter setters, int ordinal, SmiMetaData metaData, DbDataReader reader, int offset) { // Deal with large values by sending bufferLength of NoLengthLimit (== assume // CheckXetParameters will ignore requested-length checks in this case) @@ -2599,8 +2417,7 @@ private static void SetChars_FromReader(SmiEventSink_Default sink, SmiTypedGette charsWritten != 0 ) { - charsWritten = setters.SetChars(sink, ordinal, currentOffset, buffer, 0, checked((int)charsRead)); - sink.ProcessMessagesAndThrow(); + charsWritten = setters.SetChars(ordinal, currentOffset, buffer, 0, checked((int)charsRead)); checked { currentOffset += charsWritten; @@ -2609,20 +2426,18 @@ private static void SetChars_FromReader(SmiEventSink_Default sink, SmiTypedGette } // Make sure to trim any left-over data (remember to trim at end of offset, not just the amount written - setters.SetCharsLength(sink, ordinal, currentOffset); - sink.ProcessMessagesAndThrow(); + setters.SetCharsLength(ordinal, currentOffset); } - private static void SetString_FromReader(SmiEventSink_Default sink, SmiTypedGetterSetter setters, int ordinal, SmiMetaData metaData, DbDataReader reader, int offset) + private static void SetString_FromReader(SmiTypedGetterSetter setters, int ordinal, SmiMetaData metaData, DbDataReader reader, int offset) { string value = reader.GetString(ordinal); int length = CheckXetParameters(metaData.SqlDbType, metaData.MaxLength, value.Length, fieldOffset: 0, bufferLength: NoLengthLimit, bufferOffset: offset, length: NoLengthLimit); - setters.SetString(sink, ordinal, value, offset, length); - sink.ProcessMessagesAndThrow(); + setters.SetString(ordinal, value, offset, length); } - private static void SetSqlChars_LengthChecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlChars value, int offset) + private static void SetSqlChars_LengthChecked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlChars value, int offset) { int length = 0; if (!value.IsNull) @@ -2636,41 +2451,40 @@ private static void SetSqlChars_LengthChecked(SmiEventSink_Default sink, ITypedS } length = CheckXetParameters(metaData.SqlDbType, metaData.MaxLength, actualLength: NoLengthLimit, fieldOffset: 0, bufferLength: checked((int)bufferLength), bufferOffset: offset, length: checked((int)bufferLength - offset)); } - SetSqlChars_Unchecked(sink, setters, ordinal, value, 0, length); + SetSqlChars_Unchecked(setters, ordinal, value, 0, length); } - private static void SetSqlString_LengthChecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlString value, int offset) + private static void SetSqlString_LengthChecked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlString value, int offset) { if (value.IsNull) { - SetDBNull_Unchecked(sink, setters, ordinal); + SetDBNull_Unchecked(setters, ordinal); } else { string stringValue = value.Value; int length = CheckXetParameters(metaData.SqlDbType, metaData.MaxLength, actualLength: NoLengthLimit, fieldOffset: 0, bufferLength: stringValue.Length, bufferOffset: offset, length: stringValue.Length - offset); Debug.Assert(length >= 0, "value.Length was invalid!"); - SetSqlString_Unchecked(sink, setters, ordinal, metaData, value, offset, length); + SetSqlString_Unchecked(setters, ordinal, metaData, value, offset, length); } } - private static void SetString_LengthChecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, string value, int offset) + private static void SetString_LengthChecked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, string value, int offset) { int length = CheckXetParameters(metaData.SqlDbType, metaData.MaxLength, actualLength: NoLengthLimit, fieldOffset: 0, value.Length, offset, checked(value.Length - offset)); Debug.Assert(length >= 0, "value.Length was invalid!"); - SetString_Unchecked(sink, setters, ordinal, value, offset, length); + SetString_Unchecked(setters, ordinal, value, offset, length); } - private static void SetUdt_LengthChecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, object value) + private static void SetUdt_LengthChecked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, object value) { if (ADP.IsNull(value)) { - setters.SetDBNull(sink, ordinal); - sink.ProcessMessagesAndThrow(); + setters.SetDBNull(ordinal); } else { - Stream target = new SmiSettersStream(sink, setters, ordinal, metaData); + Stream target = new SmiSettersStream(setters, ordinal, metaData); SerializationHelperSql9.Serialize(target, value); } } @@ -2687,9 +2501,9 @@ private static void ThrowIfInvalidSetterAccess(SmiMetaData metaData, ExtendedClr } } - private static void ThrowIfITypedGettersIsNull(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static void ThrowIfITypedGettersIsNull(ITypedGettersV3 getters, int ordinal) { - if (IsDBNull_Unchecked(sink, getters, ordinal)) + if (IsDBNull_Unchecked(getters, ordinal)) { throw SQL.SqlNullValue(); } @@ -2978,202 +2792,179 @@ int length // Private implementation of common mappings from a given type to corresponding Smi Getter/Setter // These classes do type validation, parameter limit validation, nor coercions // - private static bool IsDBNull_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static bool IsDBNull_Unchecked(ITypedGettersV3 getters, int ordinal) { - bool result = getters.IsDBNull(sink, ordinal); - sink.ProcessMessagesAndThrow(); + bool result = getters.IsDBNull(ordinal); return result; } - private static bool GetBoolean_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static bool GetBoolean_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - bool result = getters.GetBoolean(sink, ordinal); - sink.ProcessMessagesAndThrow(); + bool result = getters.GetBoolean(ordinal); return result; } - private static byte GetByte_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static byte GetByte_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - byte result = getters.GetByte(sink, ordinal); - sink.ProcessMessagesAndThrow(); + byte result = getters.GetByte(ordinal); return result; } - private static byte[] GetByteArray_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static byte[] GetByteArray_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - long length = getters.GetBytesLength(sink, ordinal); - sink.ProcessMessagesAndThrow(); + long length = getters.GetBytesLength(ordinal); int len = checked((int)length); byte[] buffer = new byte[len]; - getters.GetBytes(sink, ordinal, 0, buffer, 0, len); - sink.ProcessMessagesAndThrow(); + getters.GetBytes(ordinal, 0, buffer, 0, len); return buffer; } - internal static int GetBytes_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) + internal static int GetBytes_Unchecked(ITypedGettersV3 getters, int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); Debug.Assert(ordinal >= 0, $"Invalid ordinal: {ordinal}"); - Debug.Assert(sink != null, "Null SmiEventSink"); Debug.Assert(getters != null, "Null getters"); Debug.Assert(fieldOffset >= 0, $"Invalid field offset: {fieldOffset}"); Debug.Assert(buffer != null, "Null buffer"); Debug.Assert(bufferOffset >= 0 && length >= 0 && bufferOffset + length <= buffer.Length, $"Bad offset or length. bufferOffset: {bufferOffset}, length: {length}, buffer.Length{buffer.Length}"); - int result = getters.GetBytes(sink, ordinal, fieldOffset, buffer, bufferOffset, length); - sink.ProcessMessagesAndThrow(); + int result = getters.GetBytes(ordinal, fieldOffset, buffer, bufferOffset, length); return result; } - private static long GetBytesLength_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static long GetBytesLength_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - long result = getters.GetBytesLength(sink, ordinal); - sink.ProcessMessagesAndThrow(); + long result = getters.GetBytesLength(ordinal); return result; } - private static char[] GetCharArray_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static char[] GetCharArray_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - long length = getters.GetCharsLength(sink, ordinal); - sink.ProcessMessagesAndThrow(); + long length = getters.GetCharsLength(ordinal); int len = checked((int)length); char[] buffer = new char[len]; - getters.GetChars(sink, ordinal, 0, buffer, 0, len); - sink.ProcessMessagesAndThrow(); + getters.GetChars(ordinal, 0, buffer, 0, len); return buffer; } - internal static int GetChars_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) + internal static int GetChars_Unchecked(ITypedGettersV3 getters, int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); Debug.Assert(ordinal >= 0, $"Invalid ordinal: {ordinal}"); - Debug.Assert(sink != null, "Null SmiEventSink"); Debug.Assert(getters != null, "Null getters"); Debug.Assert(fieldOffset >= 0, $"Invalid field offset: {fieldOffset}"); Debug.Assert(buffer != null, "Null buffer"); Debug.Assert(bufferOffset >= 0 && length >= 0 && bufferOffset + length <= buffer.Length, $"Bad offset or length. bufferOffset: {bufferOffset}, length: {length}, buffer.Length{buffer.Length}"); - int result = getters.GetChars(sink, ordinal, fieldOffset, buffer, bufferOffset, length); - sink.ProcessMessagesAndThrow(); + int result = getters.GetChars(ordinal, fieldOffset, buffer, bufferOffset, length); return result; } - private static long GetCharsLength_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static long GetCharsLength_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - long result = getters.GetCharsLength(sink, ordinal); - sink.ProcessMessagesAndThrow(); + long result = getters.GetCharsLength(ordinal); return result; } - private static DateTime GetDateTime_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static DateTime GetDateTime_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - DateTime result = getters.GetDateTime(sink, ordinal); - sink.ProcessMessagesAndThrow(); + DateTime result = getters.GetDateTime(ordinal); return result; } - private static DateTimeOffset GetDateTimeOffset_Unchecked(SmiEventSink_Default sink, SmiTypedGetterSetter getters, int ordinal) + private static DateTimeOffset GetDateTimeOffset_Unchecked(SmiTypedGetterSetter getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - DateTimeOffset result = getters.GetDateTimeOffset(sink, ordinal); - sink.ProcessMessagesAndThrow(); + DateTimeOffset result = getters.GetDateTimeOffset(ordinal); return result; } - private static double GetDouble_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static double GetDouble_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - double result = getters.GetDouble(sink, ordinal); - sink.ProcessMessagesAndThrow(); + double result = getters.GetDouble(ordinal); return result; } - private static Guid GetGuid_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static Guid GetGuid_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - Guid result = getters.GetGuid(sink, ordinal); - sink.ProcessMessagesAndThrow(); + Guid result = getters.GetGuid(ordinal); return result; } - private static short GetInt16_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static short GetInt16_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - short result = getters.GetInt16(sink, ordinal); - sink.ProcessMessagesAndThrow(); + short result = getters.GetInt16(ordinal); return result; } - private static int GetInt32_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static int GetInt32_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - int result = getters.GetInt32(sink, ordinal); - sink.ProcessMessagesAndThrow(); + int result = getters.GetInt32(ordinal); return result; } - private static long GetInt64_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static long GetInt64_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - long result = getters.GetInt64(sink, ordinal); - sink.ProcessMessagesAndThrow(); + long result = getters.GetInt64(ordinal); return result; } - private static float GetSingle_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static float GetSingle_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - float result = getters.GetSingle(sink, ordinal); - sink.ProcessMessagesAndThrow(); + float result = getters.GetSingle(ordinal); return result; } - private static SqlBinary GetSqlBinary_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static SqlBinary GetSqlBinary_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - byte[] buffer = GetByteArray_Unchecked(sink, getters, ordinal); + byte[] buffer = GetByteArray_Unchecked(getters, ordinal); return new SqlBinary(buffer); } - private static SqlDecimal GetSqlDecimal_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static SqlDecimal GetSqlDecimal_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - SqlDecimal result = getters.GetSqlDecimal(sink, ordinal); - sink.ProcessMessagesAndThrow(); + SqlDecimal result = getters.GetSqlDecimal(ordinal); return result; } - private static SqlMoney GetSqlMoney_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static SqlMoney GetSqlMoney_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - long temp = getters.GetInt64(sink, ordinal); - sink.ProcessMessagesAndThrow(); + long temp = getters.GetInt64(ordinal); #if NET return SqlMoney.FromTdsValue(temp); #else @@ -3181,67 +2972,52 @@ private static SqlMoney GetSqlMoney_Unchecked(SmiEventSink_Default sink, ITypedG #endif } - private static SqlXml GetSqlXml_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiContext context) + private static SqlXml GetSqlXml_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); -#if NETFRAMEWORK - // allow context to be null so strongly-typed getters can use - // this method without having to pass along the almost-never-used context as a parameter - // Looking the context up like this will be slightly slower, but still correct behavior - // since it's only used to get a scratch stream. - if (context == null && InOutOfProcHelper.InProc) - { - context = SmiContextFactory.Instance.GetCurrentContext(); // In the future we need to push the context checking to a higher level - } -#endif + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); + // Note: must make a copy of getter stream, since it will be used beyond // this method (valid lifetime of getters is limited). - Stream s = new SmiGettersStream(sink, getters, ordinal, SmiMetaData.DefaultXml); - Stream copy = ValueUtilsSmi.CopyIntoNewSmiScratchStream(s, sink, context); - SqlXml result = new(copy); - return result; + Stream s = new SmiGettersStream(getters, ordinal, SmiMetaData.DefaultXml); + Stream copy = CopyIntoNewSmiScratchStream(s); + return new SqlXml(copy); } - private static string GetString_Unchecked(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal) + private static string GetString_Unchecked(ITypedGettersV3 getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); // Note: depending on different getters, the result string may be truncated, e.g. for // Inproc process, the getter is InProcRecordBuffer (implemented in SqlAcess), string will be // truncated to 4000 (if length is more than 4000). If MemoryRecordBuffer getter is used, data // is not truncated. Please refer VSDD 479655 for more detailed information regarding the string length. - string result = getters.GetString(sink, ordinal); - sink.ProcessMessagesAndThrow(); + string result = getters.GetString(ordinal); return result; } - private static TimeSpan GetTimeSpan_Unchecked(SmiEventSink_Default sink, SmiTypedGetterSetter getters, int ordinal) + private static TimeSpan GetTimeSpan_Unchecked(SmiTypedGetterSetter getters, int ordinal) { - Debug.Assert(!IsDBNull_Unchecked(sink, getters, ordinal)); + Debug.Assert(!IsDBNull_Unchecked(getters, ordinal)); - TimeSpan result = getters.GetTimeSpan(sink, ordinal); - sink.ProcessMessagesAndThrow(); + TimeSpan result = getters.GetTimeSpan(ordinal); return result; } - private static void SetBoolean_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, bool value) + private static void SetBoolean_Unchecked(ITypedSettersV3 setters, int ordinal, bool value) { - setters.SetBoolean(sink, ordinal, value); - sink.ProcessMessagesAndThrow(); + setters.SetBoolean(ordinal, value); } - private static void SetByteArray_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, byte[] buffer, int bufferOffset, int length) + private static void SetByteArray_Unchecked(ITypedSettersV3 setters, int ordinal, byte[] buffer, int bufferOffset, int length) { if (length > 0) { - setters.SetBytes(sink, ordinal, 0, buffer, bufferOffset, length); - sink.ProcessMessagesAndThrow(); + setters.SetBytes(ordinal, 0, buffer, bufferOffset, length); } - setters.SetBytesLength(sink, ordinal, length); - sink.ProcessMessagesAndThrow(); + setters.SetBytesLength(ordinal, length); } - private static void SetStream_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metadata, StreamDataFeed feed) + private static void SetStream_Unchecked(ITypedSettersV3 setters, int ordinal, SmiMetaData metadata, StreamDataFeed feed) { long len = metadata.MaxLength; byte[] buff = new byte[DefaultBinaryBufferSize]; @@ -3263,17 +3039,15 @@ private static void SetStream_Unchecked(SmiEventSink_Default sink, ITypedSetters break; } - setters.SetBytes(sink, ordinal, nWritten, buff, 0, nRead); - sink.ProcessMessagesAndThrow(); + setters.SetBytes(ordinal, nWritten, buff, 0, nRead); nWritten += nRead; } while (len <= 0 || nWritten < len); - setters.SetBytesLength(sink, ordinal, nWritten); - sink.ProcessMessagesAndThrow(); + setters.SetBytesLength(ordinal, nWritten); } - private static void SetTextReader_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metadata, TextDataFeed feed) + private static void SetTextReader_Unchecked(ITypedSettersV3 setters, int ordinal, SmiMetaData metadata, TextDataFeed feed) { long len = metadata.MaxLength; char[] buff = new char[DefaultTextBufferSize]; @@ -3295,175 +3069,151 @@ private static void SetTextReader_Unchecked(SmiEventSink_Default sink, ITypedSet break; } - setters.SetChars(sink, ordinal, nWritten, buff, 0, nRead); - sink.ProcessMessagesAndThrow(); + setters.SetChars(ordinal, nWritten, buff, 0, nRead); nWritten += nRead; } while (len <= 0 || nWritten < len); - setters.SetCharsLength(sink, ordinal, nWritten); - sink.ProcessMessagesAndThrow(); + setters.SetCharsLength(ordinal, nWritten); } - private static void SetByte_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, byte value) + private static void SetByte_Unchecked(ITypedSettersV3 setters, int ordinal, byte value) { - setters.SetByte(sink, ordinal, value); - sink.ProcessMessagesAndThrow(); + setters.SetByte(ordinal, value); } - private static int SetBytes_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) + private static int SetBytes_Unchecked(ITypedSettersV3 setters, int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) { - int result = setters.SetBytes(sink, ordinal, fieldOffset, buffer, bufferOffset, length); - sink.ProcessMessagesAndThrow(); + int result = setters.SetBytes(ordinal, fieldOffset, buffer, bufferOffset, length); return result; } - private static void SetCharArray_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, char[] buffer, int bufferOffset, int length) + private static void SetCharArray_Unchecked(ITypedSettersV3 setters, int ordinal, char[] buffer, int bufferOffset, int length) { if (length > 0) { - setters.SetChars(sink, ordinal, 0, buffer, bufferOffset, length); - sink.ProcessMessagesAndThrow(); + setters.SetChars(ordinal, 0, buffer, bufferOffset, length); } - setters.SetCharsLength(sink, ordinal, length); - sink.ProcessMessagesAndThrow(); + setters.SetCharsLength(ordinal, length); } - private static int SetChars_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) + private static int SetChars_Unchecked(ITypedSettersV3 setters, int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) { - int result = setters.SetChars(sink, ordinal, fieldOffset, buffer, bufferOffset, length); - sink.ProcessMessagesAndThrow(); + int result = setters.SetChars(ordinal, fieldOffset, buffer, bufferOffset, length); return result; } - private static void SetDBNull_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal) + private static void SetDBNull_Unchecked(ITypedSettersV3 setters, int ordinal) { - setters.SetDBNull(sink, ordinal); - sink.ProcessMessagesAndThrow(); + setters.SetDBNull(ordinal); } - private static void SetDecimal_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, decimal value) + private static void SetDecimal_Unchecked(ITypedSettersV3 setters, int ordinal, decimal value) { - setters.SetSqlDecimal(sink, ordinal, new SqlDecimal(value)); - sink.ProcessMessagesAndThrow(); + setters.SetSqlDecimal(ordinal, new SqlDecimal(value)); } - private static void SetDateTime_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, DateTime value) + private static void SetDateTime_Unchecked(ITypedSettersV3 setters, int ordinal, DateTime value) { - setters.SetDateTime(sink, ordinal, value); - sink.ProcessMessagesAndThrow(); + setters.SetDateTime(ordinal, value); } - private static void SetDateTime2_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, DateTime value) + private static void SetDateTime2_Unchecked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, DateTime value) { Debug.Assert(metaData.SqlDbType == SqlDbType.Variant, "Invalid type. This should be called only when the type is variant."); - setters.SetVariantMetaData(sink, ordinal, SmiMetaData.DefaultDateTime2); - setters.SetDateTime(sink, ordinal, value); - sink.ProcessMessagesAndThrow(); + setters.SetVariantMetaData(ordinal, SmiMetaData.DefaultDateTime2); + setters.SetDateTime(ordinal, value); } - private static void SetDate_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, DateTime value) + private static void SetDate_Unchecked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, DateTime value) { Debug.Assert(metaData.SqlDbType == SqlDbType.Variant, "Invalid type. This should be called only when the type is variant."); - setters.SetVariantMetaData(sink, ordinal, SmiMetaData.DefaultDate); - setters.SetDateTime(sink, ordinal, value); - sink.ProcessMessagesAndThrow(); + setters.SetVariantMetaData(ordinal, SmiMetaData.DefaultDate); + setters.SetDateTime(ordinal, value); } - private static void SetTimeSpan_Unchecked(SmiEventSink_Default sink, SmiTypedGetterSetter setters, int ordinal, TimeSpan value) + private static void SetTimeSpan_Unchecked(SmiTypedGetterSetter setters, int ordinal, TimeSpan value) { - setters.SetTimeSpan(sink, ordinal, value); - sink.ProcessMessagesAndThrow(); + setters.SetTimeSpan(ordinal, value); } - private static void SetDateTimeOffset_Unchecked(SmiEventSink_Default sink, SmiTypedGetterSetter setters, int ordinal, DateTimeOffset value) + private static void SetDateTimeOffset_Unchecked(SmiTypedGetterSetter setters, int ordinal, DateTimeOffset value) { - setters.SetDateTimeOffset(sink, ordinal, value); - sink.ProcessMessagesAndThrow(); + setters.SetDateTimeOffset(ordinal, value); } - private static void SetDouble_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, double value) + private static void SetDouble_Unchecked(ITypedSettersV3 setters, int ordinal, double value) { - setters.SetDouble(sink, ordinal, value); - sink.ProcessMessagesAndThrow(); + setters.SetDouble(ordinal, value); } - private static void SetGuid_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, Guid value) + private static void SetGuid_Unchecked(ITypedSettersV3 setters, int ordinal, Guid value) { - setters.SetGuid(sink, ordinal, value); - sink.ProcessMessagesAndThrow(); + setters.SetGuid(ordinal, value); } - private static void SetInt16_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, short value) + private static void SetInt16_Unchecked(ITypedSettersV3 setters, int ordinal, short value) { - setters.SetInt16(sink, ordinal, value); - sink.ProcessMessagesAndThrow(); + setters.SetInt16(ordinal, value); } - private static void SetInt32_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, int value) + private static void SetInt32_Unchecked(ITypedSettersV3 setters, int ordinal, int value) { - setters.SetInt32(sink, ordinal, value); - sink.ProcessMessagesAndThrow(); + setters.SetInt32(ordinal, value); } - private static void SetInt64_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, long value) + private static void SetInt64_Unchecked(ITypedSettersV3 setters, int ordinal, long value) { - setters.SetInt64(sink, ordinal, value); - sink.ProcessMessagesAndThrow(); + setters.SetInt64(ordinal, value); } - private static void SetSingle_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, float value) + private static void SetSingle_Unchecked(ITypedSettersV3 setters, int ordinal, float value) { - setters.SetSingle(sink, ordinal, value); - sink.ProcessMessagesAndThrow(); + setters.SetSingle(ordinal, value); } - private static void SetSqlBinary_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SqlBinary value, int offset, int length) + private static void SetSqlBinary_Unchecked(ITypedSettersV3 setters, int ordinal, SqlBinary value, int offset, int length) { if (value.IsNull) { - setters.SetDBNull(sink, ordinal); + setters.SetDBNull(ordinal); } else { - SetByteArray_Unchecked(sink, setters, ordinal, value.Value, offset, length); + SetByteArray_Unchecked(setters, ordinal, value.Value, offset, length); } - sink.ProcessMessagesAndThrow(); } - private static void SetSqlBoolean_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SqlBoolean value) + private static void SetSqlBoolean_Unchecked(ITypedSettersV3 setters, int ordinal, SqlBoolean value) { if (value.IsNull) { - setters.SetDBNull(sink, ordinal); + setters.SetDBNull(ordinal); } else { - setters.SetBoolean(sink, ordinal, value.Value); + setters.SetBoolean(ordinal, value.Value); } - sink.ProcessMessagesAndThrow(); } - private static void SetSqlByte_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SqlByte value) + private static void SetSqlByte_Unchecked(ITypedSettersV3 setters, int ordinal, SqlByte value) { if (value.IsNull) { - setters.SetDBNull(sink, ordinal); + setters.SetDBNull(ordinal); } else { - setters.SetByte(sink, ordinal, value.Value); + setters.SetByte(ordinal, value.Value); } - sink.ProcessMessagesAndThrow(); } // note: length < 0 indicates write everything - private static void SetSqlBytes_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SqlBytes value, int offset, long length) + private static void SetSqlBytes_Unchecked(ITypedSettersV3 setters, int ordinal, SqlBytes value, int offset, long length) { if (value.IsNull) { - setters.SetDBNull(sink, ordinal); - sink.ProcessMessagesAndThrow(); + setters.SetDBNull(ordinal); } else { @@ -3489,8 +3239,7 @@ private static void SetSqlBytes_Unchecked(SmiEventSink_Default sink, ITypedSette bytesWritten != 0 ) { - bytesWritten = setters.SetBytes(sink, ordinal, currentOffset, buffer, 0, checked((int)bytesRead)); - sink.ProcessMessagesAndThrow(); + bytesWritten = setters.SetBytes(ordinal, currentOffset, buffer, 0, checked((int)bytesRead)); checked { currentOffset += bytesWritten; @@ -3502,17 +3251,15 @@ private static void SetSqlBytes_Unchecked(SmiEventSink_Default sink, ITypedSette } // Make sure to trim any left-over data - setters.SetBytesLength(sink, ordinal, currentOffset); - sink.ProcessMessagesAndThrow(); + setters.SetBytesLength(ordinal, currentOffset); } } - private static void SetSqlChars_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SqlChars value, int offset, int length) + private static void SetSqlChars_Unchecked(ITypedSettersV3 setters, int ordinal, SqlChars value, int offset, int length) { if (value.IsNull) { - setters.SetDBNull(sink, ordinal); - sink.ProcessMessagesAndThrow(); + setters.SetDBNull(ordinal); } else { @@ -3538,8 +3285,7 @@ private static void SetSqlChars_Unchecked(SmiEventSink_Default sink, ITypedSette charsWritten != 0 ) { - charsWritten = setters.SetChars(sink, ordinal, currentOffset, buffer, 0, checked((int)charsRead)); - sink.ProcessMessagesAndThrow(); + charsWritten = setters.SetChars(ordinal, currentOffset, buffer, 0, checked((int)charsRead)); checked { currentOffset += charsWritten; @@ -3548,144 +3294,132 @@ private static void SetSqlChars_Unchecked(SmiEventSink_Default sink, ITypedSette } // Make sure to trim any left-over data - setters.SetCharsLength(sink, ordinal, currentOffset); - sink.ProcessMessagesAndThrow(); + setters.SetCharsLength(ordinal, currentOffset); } } - private static void SetSqlDateTime_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SqlDateTime value) + private static void SetSqlDateTime_Unchecked(ITypedSettersV3 setters, int ordinal, SqlDateTime value) { if (value.IsNull) { - setters.SetDBNull(sink, ordinal); + setters.SetDBNull(ordinal); } else { - setters.SetDateTime(sink, ordinal, value.Value); + setters.SetDateTime(ordinal, value.Value); } - sink.ProcessMessagesAndThrow(); } - private static void SetSqlDecimal_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SqlDecimal value) + private static void SetSqlDecimal_Unchecked(ITypedSettersV3 setters, int ordinal, SqlDecimal value) { if (value.IsNull) { - setters.SetDBNull(sink, ordinal); + setters.SetDBNull(ordinal); } else { - setters.SetSqlDecimal(sink, ordinal, value); + setters.SetSqlDecimal(ordinal, value); } - sink.ProcessMessagesAndThrow(); } - private static void SetSqlDouble_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SqlDouble value) + private static void SetSqlDouble_Unchecked(ITypedSettersV3 setters, int ordinal, SqlDouble value) { if (value.IsNull) { - setters.SetDBNull(sink, ordinal); + setters.SetDBNull(ordinal); } else { - setters.SetDouble(sink, ordinal, value.Value); + setters.SetDouble(ordinal, value.Value); } - sink.ProcessMessagesAndThrow(); } - private static void SetSqlGuid_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SqlGuid value) + private static void SetSqlGuid_Unchecked(ITypedSettersV3 setters, int ordinal, SqlGuid value) { if (value.IsNull) { - setters.SetDBNull(sink, ordinal); + setters.SetDBNull(ordinal); } else { - setters.SetGuid(sink, ordinal, value.Value); + setters.SetGuid(ordinal, value.Value); } - sink.ProcessMessagesAndThrow(); } - private static void SetSqlInt16_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SqlInt16 value) + private static void SetSqlInt16_Unchecked(ITypedSettersV3 setters, int ordinal, SqlInt16 value) { if (value.IsNull) { - setters.SetDBNull(sink, ordinal); + setters.SetDBNull(ordinal); } else { - setters.SetInt16(sink, ordinal, value.Value); + setters.SetInt16(ordinal, value.Value); } - sink.ProcessMessagesAndThrow(); } - private static void SetSqlInt32_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SqlInt32 value) + private static void SetSqlInt32_Unchecked(ITypedSettersV3 setters, int ordinal, SqlInt32 value) { if (value.IsNull) { - setters.SetDBNull(sink, ordinal); + setters.SetDBNull(ordinal); } else { - setters.SetInt32(sink, ordinal, value.Value); + setters.SetInt32(ordinal, value.Value); } - sink.ProcessMessagesAndThrow(); } - private static void SetSqlInt64_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SqlInt64 value) + private static void SetSqlInt64_Unchecked(ITypedSettersV3 setters, int ordinal, SqlInt64 value) { if (value.IsNull) { - setters.SetDBNull(sink, ordinal); + setters.SetDBNull(ordinal); } else { - setters.SetInt64(sink, ordinal, value.Value); + setters.SetInt64(ordinal, value.Value); } - sink.ProcessMessagesAndThrow(); } - private static void SetSqlMoney_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlMoney value) + private static void SetSqlMoney_Unchecked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlMoney value) { if (value.IsNull) { - setters.SetDBNull(sink, ordinal); + setters.SetDBNull(ordinal); } else { if (metaData.SqlDbType == SqlDbType.Variant) { - setters.SetVariantMetaData(sink, ordinal, SmiMetaData.DefaultMoney); - sink.ProcessMessagesAndThrow(); + setters.SetVariantMetaData(ordinal, SmiMetaData.DefaultMoney); } #if NET - setters.SetInt64(sink, ordinal, value.GetTdsValue()); + setters.SetInt64(ordinal, value.GetTdsValue()); #else - setters.SetInt64(sink, ordinal, SqlTypeWorkarounds.SqlMoneyToSqlInternalRepresentation(value)); + setters.SetInt64(ordinal, SqlTypeWorkarounds.SqlMoneyToSqlInternalRepresentation(value)); #endif } - sink.ProcessMessagesAndThrow(); } - private static void SetSqlSingle_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SqlSingle value) + private static void SetSqlSingle_Unchecked(ITypedSettersV3 setters, int ordinal, SqlSingle value) { if (value.IsNull) { - setters.SetDBNull(sink, ordinal); + setters.SetDBNull(ordinal); } else { - setters.SetSingle(sink, ordinal, value.Value); + setters.SetSingle(ordinal, value.Value); } - sink.ProcessMessagesAndThrow(); } - private static void SetSqlString_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlString value, int offset, int length) + private static void SetSqlString_Unchecked(ITypedSettersV3 setters, int ordinal, SmiMetaData metaData, SqlString value, int offset, int length) { if (value.IsNull) { - setters.SetDBNull(sink, ordinal); - sink.ProcessMessagesAndThrow(); + setters.SetDBNull(ordinal); } else { @@ -3701,27 +3435,25 @@ private static void SetSqlString_Unchecked(SmiEventSink_Default sink, ITypedSett value.SqlCompareOptions, userDefinedType: null ); - setters.SetVariantMetaData(sink, ordinal, metaData); - sink.ProcessMessagesAndThrow(); + setters.SetVariantMetaData(ordinal, metaData); } - SetString_Unchecked(sink, setters, ordinal, value.Value, offset, length); + SetString_Unchecked(setters, ordinal, value.Value, offset, length); } } - private static void SetSqlXml_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, SqlXml value) + private static void SetSqlXml_Unchecked(ITypedSettersV3 setters, int ordinal, SqlXml value) { if (value.IsNull) { - setters.SetDBNull(sink, ordinal); - sink.ProcessMessagesAndThrow(); + setters.SetDBNull(ordinal); } else { - SetXmlReader_Unchecked(sink, setters, ordinal, value.CreateReader()); + SetXmlReader_Unchecked(setters, ordinal, value.CreateReader()); } } - private static void SetXmlReader_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, XmlReader xmlReader) + private static void SetXmlReader_Unchecked(ITypedSettersV3 setters, int ordinal, XmlReader xmlReader) { // set up writer XmlWriterSettings WriterSettings = new XmlWriterSettings @@ -3732,7 +3464,7 @@ private static void SetXmlReader_Unchecked(SmiEventSink_Default sink, ITypedSett OmitXmlDeclaration = true }; - using (Stream target = new SmiSettersStream(sink, setters, ordinal, SmiMetaData.DefaultXml)) + using (Stream target = new SmiSettersStream(setters, ordinal, SmiMetaData.DefaultXml)) using (XmlWriter xmlWriter = XmlWriter.Create(target, WriterSettings)) { // now spool the data into the writer (WriteNode will call Read()) @@ -3743,19 +3475,16 @@ private static void SetXmlReader_Unchecked(SmiEventSink_Default sink, ITypedSett } xmlWriter.Flush(); } - sink.ProcessMessagesAndThrow(); } - private static void SetString_Unchecked(SmiEventSink_Default sink, ITypedSettersV3 setters, int ordinal, string value, int offset, int length) + private static void SetString_Unchecked(ITypedSettersV3 setters, int ordinal, string value, int offset, int length) { - setters.SetString(sink, ordinal, value, offset, length); - sink.ProcessMessagesAndThrow(); + setters.SetString(ordinal, value, offset, length); } // Set a DbDataReader to a Structured+MultiValued setter (table type) // Assumes metaData correctly describes the reader's shape, and consumes only the current resultset private static void SetDbDataReader_Unchecked( - SmiEventSink_Default sink, SmiTypedGetterSetter setters, int ordinal, SmiMetaData metaData, @@ -3763,24 +3492,20 @@ DbDataReader value ) { // Get the target gettersetter - setters = setters.GetTypedGetterSetter(sink, ordinal); - sink.ProcessMessagesAndThrow(); + setters = setters.GetTypedGetterSetter(ordinal); // Iterate over all rows in the current set of results while (value.Read()) { - setters.NewElement(sink); - sink.ProcessMessagesAndThrow(); + setters.NewElement(); - FillCompatibleSettersFromReader(sink, setters, metaData.FieldMetaData, value); + FillCompatibleSettersFromReader(setters, metaData.FieldMetaData, value); } - setters.EndElements(sink); - sink.ProcessMessagesAndThrow(); + setters.EndElements(); } private static void SetIEnumerableOfSqlDataRecord_Unchecked( - SmiEventSink_Default sink, SmiTypedGetterSetter setters, int ordinal, SmiMetaData metaData, @@ -3789,8 +3514,7 @@ ParameterPeekAheadValue peekAhead ) { // Get target gettersetter - setters = setters.GetTypedGetterSetter(sink, ordinal); - sink.ProcessMessagesAndThrow(); + setters = setters.GetTypedGetterSetter(ordinal); IEnumerator enumerator = null; try @@ -3810,9 +3534,8 @@ ParameterPeekAheadValue peekAhead enumerator = peekAhead.Enumerator; // send the first record that was obtained earlier - setters.NewElement(sink); - sink.ProcessMessagesAndThrow(); - FillCompatibleSettersFromRecord(sink, setters, mdFields, peekAhead.FirstRecord, defaults); + setters.NewElement(); + FillCompatibleSettersFromRecord(setters, mdFields, peekAhead.FirstRecord, defaults); recordNumber++; } else @@ -3822,8 +3545,7 @@ ParameterPeekAheadValue peekAhead while (enumerator.MoveNext()) { - setters.NewElement(sink); - sink.ProcessMessagesAndThrow(); + setters.NewElement(); SqlDataRecord record = enumerator.Current; @@ -3840,12 +3562,11 @@ ParameterPeekAheadValue peekAhead } } - FillCompatibleSettersFromRecord(sink, setters, mdFields, record, defaults); + FillCompatibleSettersFromRecord(setters, mdFields, record, defaults); recordNumber++; } - setters.EndElements(sink); - sink.ProcessMessagesAndThrow(); + setters.EndElements(); } finally @@ -3858,7 +3579,6 @@ ParameterPeekAheadValue peekAhead } private static void SetDataTable_Unchecked( - SmiEventSink_Default sink, SmiTypedGetterSetter setters, int ordinal, SmiMetaData metaData, @@ -3866,8 +3586,7 @@ DataTable value ) { // Get the target gettersetter - setters = setters.GetTypedGetterSetter(sink, ordinal); - sink.ProcessMessagesAndThrow(); + setters = setters.GetTypedGetterSetter(ordinal); // iterate over all records // if first record was obtained earlier, use it prior to pulling more @@ -3878,8 +3597,7 @@ DataTable value } foreach (DataRow row in value.Rows) { - setters.NewElement(sink); - sink.ProcessMessagesAndThrow(); + setters.NewElement(); // Set all columns in the record for (int i = 0; i < metaData.FieldMetaData.Count; i++) @@ -3887,7 +3605,7 @@ DataTable value SmiMetaData fieldMetaData = metaData.FieldMetaData[i]; if (row.IsNull(i)) { - SetDBNull_Unchecked(sink, setters, i); + SetDBNull_Unchecked(setters, i); } else { @@ -3897,37 +3615,23 @@ DataTable value if (ExtendedClrTypeCode.Invalid == cellTypes[i]) { cellTypes[i] = MetaDataUtilsSmi.DetermineExtendedTypeCodeForUseWithSqlDbType( - fieldMetaData.SqlDbType, fieldMetaData.IsMultiValued, cellValue, fieldMetaData.Type -#if NETFRAMEWORK - ,// TODO: this version works for shipping VS2008, since only 2008 supports TVPs at this time. - // Need a better story for smi versioning of ValueUtilsSmi post-VS2008 - SmiContextFactory.Sql2008Version -#endif - ); + fieldMetaData.SqlDbType, + fieldMetaData.IsMultiValued, + cellValue, + fieldMetaData.Type); } - SetCompatibleValueV200(sink, setters, i, fieldMetaData, cellValue, cellTypes[i], 0, null); + SetCompatibleValueV200(setters, i, fieldMetaData, cellValue, cellTypes[i], 0, null); } } } - setters.EndElements(sink); - sink.ProcessMessagesAndThrow(); + setters.EndElements(); } // spool a Stream into a scratch stream from the Smi interface and return it as a Stream - internal static Stream CopyIntoNewSmiScratchStream(Stream source, SmiEventSink_Default sink, SmiContext context) + internal static Stream CopyIntoNewSmiScratchStream(Stream source) { - Stream dest = null; -#if NETFRAMEWORK - if (context != null) - { - dest = new SqlClientWrapperSmiStream(sink, context.GetScratchStream(sink)); - } -#endif - if (dest == null) - { - dest = new MemoryStream(); - } + Stream dest = new MemoryStream(); int chunkSize; if (source.CanSeek && source.Length > MaxByteChunkSize) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.netfx.cs deleted file mode 100644 index 7bed34713b..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Server/ValueUtilsSmi.netfx.cs +++ /dev/null @@ -1,508 +0,0 @@ -using System; -using System.Data; -using System.Data.SqlTypes; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using Microsoft.Data.Common; -using Microsoft.Data.SqlTypes; - -namespace Microsoft.Data.SqlClient.Server -{ - // Utilities for manipulating values with the Smi interface. - // - // THIS CLASS IS BUILT ON TOP OF THE SMI INTERFACE -- SMI SHOULD NOT DEPEND ON IT! - // - // These are all based off of knowing the clr type of the value - // as an ExtendedClrTypeCode enum for rapid access (lookup in static array is best, if possible). - internal static partial class ValueUtilsSmi - { - - internal static SqlSequentialStreamSmi GetSequentialStream(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData, bool bypassTypeCheck = false) - { - Debug.Assert(!ValueUtilsSmi.IsDBNull_Unchecked(sink, getters, ordinal), "Should not try to get a SqlSequentialStreamSmi on a null column"); - ThrowIfITypedGettersIsNull(sink, getters, ordinal); - if ((!bypassTypeCheck) && (!CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.Stream))) - { - throw ADP.InvalidCast(); - } - - // This will advance the column to ordinal - long length = GetBytesLength_Unchecked(sink, getters, ordinal); - return new SqlSequentialStreamSmi(sink, getters, ordinal, length); - } - - internal static SqlSequentialTextReaderSmi GetSequentialTextReader(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) - { - Debug.Assert(!ValueUtilsSmi.IsDBNull_Unchecked(sink, getters, ordinal), "Should not try to get a SqlSequentialTextReaderSmi on a null column"); - ThrowIfITypedGettersIsNull(sink, getters, ordinal); - if (!CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.TextReader)) - { - throw ADP.InvalidCast(); - } - - // This will advance the column to ordinal - long length = GetCharsLength_Unchecked(sink, getters, ordinal); - return new SqlSequentialTextReaderSmi(sink, getters, ordinal, length); - } - - internal static Stream GetStream(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData, bool bypassTypeCheck = false) - { - bool isDbNull = ValueUtilsSmi.IsDBNull_Unchecked(sink, getters, ordinal); - - // If a sql_variant, get the internal type - if (!bypassTypeCheck) - { - if ((!isDbNull) && (metaData.SqlDbType == SqlDbType.Variant)) - { - metaData = getters.GetVariantType(sink, ordinal); - } - // If the SqlDbType is still variant, then it must contain null, so don't throw InvalidCast - if ((metaData.SqlDbType != SqlDbType.Variant) && (!CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.Stream))) - { - throw ADP.InvalidCast(); - } - } - - byte[] data; - if (isDbNull) - { - // "null" stream - data = new byte[0]; - } - else - { - // Read all data - data = GetByteArray_Unchecked(sink, getters, ordinal); - } - - // Wrap data in pre-built object - return new MemoryStream(data, writable: false); - } - - internal static TextReader GetTextReader(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData) - { - bool isDbNull = ValueUtilsSmi.IsDBNull_Unchecked(sink, getters, ordinal); - - // If a sql_variant, get the internal type - if ((!isDbNull) && (metaData.SqlDbType == SqlDbType.Variant)) - { - metaData = getters.GetVariantType(sink, ordinal); - } - // If the SqlDbType is still variant, then it must contain null, so don't throw InvalidCast - if ((metaData.SqlDbType != SqlDbType.Variant) && (!CanAccessGetterDirectly(metaData, ExtendedClrTypeCode.TextReader))) - { - throw ADP.InvalidCast(); - } - - string data; - if (isDbNull) - { - // "null" textreader - data = string.Empty; - } - else - { - // Read all data - data = GetString_Unchecked(sink, getters, ordinal); - } - - // Wrap in pre-built object - return new StringReader(data); - } - - // calling GetTimeSpan on possibly v100 SMI - internal static TimeSpan GetTimeSpan(SmiEventSink_Default sink, ITypedGettersV3 getters, int ordinal, SmiMetaData metaData, bool gettersSupport2008DateTime) - { - if (gettersSupport2008DateTime) - { - return GetTimeSpan(sink, (SmiTypedGetterSetter)getters, ordinal, metaData); - } - ThrowIfITypedGettersIsNull(sink, getters, ordinal); - object obj = GetValue(sink, getters, ordinal, metaData); - if (obj == null) - { - throw ADP.InvalidCast(); - } - return (TimeSpan)obj; - } - - internal static SqlBuffer.StorageType SqlDbTypeToStorageType(SqlDbType dbType) - { - int index = unchecked((int)dbType); - Debug.Assert(index >= 0 && index < s_dbTypeToStorageType.Length, string.Format(CultureInfo.InvariantCulture, "Unexpected dbType value: {0}", dbType)); - return s_dbTypeToStorageType[index]; - } - - private static void GetNullOutputParameterSmi(SmiMetaData metaData, SqlBuffer targetBuffer, ref object result) - { - if (SqlDbType.Udt == metaData.SqlDbType) - { - result = NullUdtInstance(metaData); - } - else - { - SqlBuffer.StorageType stype = SqlDbTypeToStorageType(metaData.SqlDbType); - if (SqlBuffer.StorageType.Empty == stype) - { - result = DBNull.Value; - } - else if (SqlBuffer.StorageType.SqlBinary == stype) - { - // special case SqlBinary, 'cause tds parser never sets SqlBuffer to null, just to empty! - targetBuffer.SqlBinary = SqlBinary.Null; - } - else if (SqlBuffer.StorageType.SqlGuid == stype) - { - targetBuffer.SqlGuid = SqlGuid.Null; - } - else - { - targetBuffer.SetToNullOfType(stype); - } - } - } - - // UDTs and null variants come back via return value, all else is via targetBuffer. - // implements SqlClient 2.0-compatible output parameter semantics - internal static object GetOutputParameterV3Smi( - SmiEventSink_Default sink, // event sink for errors - ITypedGettersV3 getters, // getters interface to grab value from - int ordinal, // parameter within getters - SmiMetaData metaData, // Getter's type for this ordinal - SmiContext context, // used to obtain scratch streams - SqlBuffer targetBuffer // destination - ) - { - object result = null; // Workaround for UDT hack in non-Smi code paths. - if (IsDBNull_Unchecked(sink, getters, ordinal)) - { - GetNullOutputParameterSmi(metaData, targetBuffer, ref result); - } - else - { - switch (metaData.SqlDbType) - { - case SqlDbType.BigInt: - targetBuffer.Int64 = GetInt64_Unchecked(sink, getters, ordinal); - break; - case SqlDbType.Binary: - case SqlDbType.Image: - case SqlDbType.Timestamp: - case SqlDbType.VarBinary: - targetBuffer.SqlBinary = GetSqlBinary_Unchecked(sink, getters, ordinal); - break; - case SqlDbType.Bit: - targetBuffer.Boolean = GetBoolean_Unchecked(sink, getters, ordinal); - break; - case SqlDbType.NChar: - case SqlDbType.NText: - case SqlDbType.NVarChar: - case SqlDbType.Char: - case SqlDbType.VarChar: - case SqlDbType.Text: - targetBuffer.SetToString(GetString_Unchecked(sink, getters, ordinal)); - break; - case SqlDbType.DateTime: - case SqlDbType.SmallDateTime: - { - SqlDateTime dt = new(GetDateTime_Unchecked(sink, getters, ordinal)); - targetBuffer.SetToDateTime(dt.DayTicks, dt.TimeTicks); - break; - } - case SqlDbType.Decimal: - { - SqlDecimal dec = GetSqlDecimal_Unchecked(sink, getters, ordinal); - targetBuffer.SetToDecimal(dec.Precision, dec.Scale, dec.IsPositive, dec.Data); - break; - } - case SqlDbType.Float: - targetBuffer.Double = GetDouble_Unchecked(sink, getters, ordinal); - break; - case SqlDbType.Int: - targetBuffer.Int32 = GetInt32_Unchecked(sink, getters, ordinal); - break; - case SqlDbType.Money: - case SqlDbType.SmallMoney: - targetBuffer.SetToMoney(GetInt64_Unchecked(sink, getters, ordinal)); - break; - case SqlDbType.Real: - targetBuffer.Single = GetSingle_Unchecked(sink, getters, ordinal); - break; - case SqlDbType.UniqueIdentifier: - targetBuffer.SqlGuid = new SqlGuid(GetGuid_Unchecked(sink, getters, ordinal)); - break; - case SqlDbType.SmallInt: - targetBuffer.Int16 = GetInt16_Unchecked(sink, getters, ordinal); - break; - case SqlDbType.TinyInt: - targetBuffer.Byte = GetByte_Unchecked(sink, getters, ordinal); - break; - case SqlDbType.Variant: - // For variants, recur using the current value's sqldbtype - metaData = getters.GetVariantType(sink, ordinal); - sink.ProcessMessagesAndThrow(); - Debug.Assert(SqlDbType.Variant != metaData.SqlDbType, "Variant-within-variant not supposed to be possible!"); - GetOutputParameterV3Smi(sink, getters, ordinal, metaData, context, targetBuffer); - break; - case SqlDbType.Udt: - result = GetUdt_LengthChecked(sink, getters, ordinal, metaData); - break; - case SqlDbType.Xml: - targetBuffer.SqlXml = GetSqlXml_Unchecked(sink, getters, ordinal, null); - break; - default: - Debug.Assert(false, "Unexpected SqlDbType"); - break; - } - } - - return result; - } - - // UDTs and null variants come back via return value, all else is via targetBuffer. - // implements SqlClient 1.1-compatible output parameter semantics - internal static object GetOutputParameterV200Smi( - SmiEventSink_Default sink, // event sink for errors - SmiTypedGetterSetter getters, // getters interface to grab value from - int ordinal, // parameter within getters - SmiMetaData metaData, // Getter's type for this ordinal - SmiContext context, // used to obtain scratch streams - SqlBuffer targetBuffer // destination - ) - { - object result = null; // Workaround for UDT hack in non-Smi code paths. - if (IsDBNull_Unchecked(sink, getters, ordinal)) - { - GetNullOutputParameterSmi(metaData, targetBuffer, ref result); - } - else - { - switch (metaData.SqlDbType) - { - // new types go here - case SqlDbType.Variant: // Handle variants specifically for v200, since they could contain v200 types - // For variants, recur using the current value's sqldbtype - metaData = getters.GetVariantType(sink, ordinal); - sink.ProcessMessagesAndThrow(); - Debug.Assert(SqlDbType.Variant != metaData.SqlDbType, "Variant-within-variant not supposed to be possible!"); - GetOutputParameterV200Smi(sink, getters, ordinal, metaData, context, targetBuffer); - break; - case SqlDbType.Date: - targetBuffer.SetToDate(GetDateTime_Unchecked(sink, getters, ordinal)); - break; - case SqlDbType.DateTime2: - targetBuffer.SetToDateTime2(GetDateTime_Unchecked(sink, getters, ordinal), metaData.Scale); - break; - case SqlDbType.Time: - targetBuffer.SetToTime(GetTimeSpan_Unchecked(sink, getters, ordinal), metaData.Scale); - break; - case SqlDbType.DateTimeOffset: - targetBuffer.SetToDateTimeOffset(GetDateTimeOffset_Unchecked(sink, getters, ordinal), metaData.Scale); - break; - default: - result = GetOutputParameterV3Smi(sink, getters, ordinal, metaData, context, targetBuffer); - break; - } - } - - return result; - } - - private static readonly SqlBuffer.StorageType[] s_dbTypeToStorageType = new SqlBuffer.StorageType[] { - SqlBuffer.StorageType.Int64, // BigInt - SqlBuffer.StorageType.SqlBinary, // Binary - SqlBuffer.StorageType.Boolean, // Bit - SqlBuffer.StorageType.String, // Char - SqlBuffer.StorageType.DateTime, // DateTime - SqlBuffer.StorageType.Decimal, // Decimal - SqlBuffer.StorageType.Double, // Float - SqlBuffer.StorageType.SqlBinary, // Image - SqlBuffer.StorageType.Int32, // Int - SqlBuffer.StorageType.Money, // Money - SqlBuffer.StorageType.String, // NChar - SqlBuffer.StorageType.String, // NText - SqlBuffer.StorageType.String, // NVarChar - SqlBuffer.StorageType.Single, // Real - SqlBuffer.StorageType.SqlGuid, // UniqueIdentifier - SqlBuffer.StorageType.DateTime, // SmallDateTime - SqlBuffer.StorageType.Int16, // SmallInt - SqlBuffer.StorageType.Money, // SmallMoney - SqlBuffer.StorageType.String, // Text - SqlBuffer.StorageType.SqlBinary, // Timestamp - SqlBuffer.StorageType.Byte, // TinyInt - SqlBuffer.StorageType.SqlBinary, // VarBinary - SqlBuffer.StorageType.String, // VarChar - SqlBuffer.StorageType.Empty, // Variant - SqlBuffer.StorageType.Empty, // 24 - SqlBuffer.StorageType.SqlXml, // Xml - SqlBuffer.StorageType.Empty, // 26 - SqlBuffer.StorageType.Empty, // 27 - SqlBuffer.StorageType.Empty, // 28 - SqlBuffer.StorageType.Empty, // Udt - SqlBuffer.StorageType.Empty, // Structured - SqlBuffer.StorageType.Date, // Date - SqlBuffer.StorageType.Time, // Time - SqlBuffer.StorageType.DateTime2, // DateTime2 - SqlBuffer.StorageType.DateTimeOffset, // DateTimeOffset - }; - - internal static void FillCompatibleITypedSettersFromRecord(SmiEventSink_Default sink, ITypedSettersV3 setters, SmiMetaData[] metaData, SqlDataRecord record) - { - FillCompatibleITypedSettersFromRecord(sink, setters, metaData, record, null); - } - - internal static void FillCompatibleITypedSettersFromRecord(SmiEventSink_Default sink, ITypedSettersV3 setters, SmiMetaData[] metaData, SqlDataRecord record, SmiDefaultFieldsProperty useDefaultValues) - { - for (int i = 0; i < metaData.Length; ++i) - { - if (useDefaultValues != null && useDefaultValues[i]) - { - continue; - } - if (record.IsDBNull(i)) - { - ValueUtilsSmi.SetDBNull_Unchecked(sink, setters, i); - } - else - { - switch (metaData[i].SqlDbType) - { - case SqlDbType.BigInt: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Int64)); - SetInt64_Unchecked(sink, setters, i, record.GetInt64(i)); - break; - case SqlDbType.Binary: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlBytes)); - SetBytes_FromRecord(sink, setters, i, metaData[i], record, 0); - break; - case SqlDbType.Bit: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Boolean)); - SetBoolean_Unchecked(sink, setters, i, record.GetBoolean(i)); - break; - case SqlDbType.Char: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlChars)); - SetChars_FromRecord(sink, setters, i, metaData[i], record, 0); - break; - case SqlDbType.DateTime: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.DateTime)); - SetDateTime_Checked(sink, setters, i, metaData[i], record.GetDateTime(i)); - break; - case SqlDbType.Decimal: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlDecimal)); - SetSqlDecimal_Unchecked(sink, setters, i, record.GetSqlDecimal(i)); - break; - case SqlDbType.Float: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Double)); - SetDouble_Unchecked(sink, setters, i, record.GetDouble(i)); - break; - case SqlDbType.Image: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlBytes)); - SetBytes_FromRecord(sink, setters, i, metaData[i], record, 0); - break; - case SqlDbType.Int: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Int32)); - SetInt32_Unchecked(sink, setters, i, record.GetInt32(i)); - break; - case SqlDbType.Money: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlMoney)); - SetSqlMoney_Unchecked(sink, setters, i, metaData[i], record.GetSqlMoney(i)); - break; - case SqlDbType.NChar: - case SqlDbType.NText: - case SqlDbType.NVarChar: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlChars)); - SetChars_FromRecord(sink, setters, i, metaData[i], record, 0); - break; - case SqlDbType.Real: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Single)); - SetSingle_Unchecked(sink, setters, i, record.GetFloat(i)); - break; - case SqlDbType.UniqueIdentifier: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Guid)); - SetGuid_Unchecked(sink, setters, i, record.GetGuid(i)); - break; - case SqlDbType.SmallDateTime: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.DateTime)); - SetDateTime_Checked(sink, setters, i, metaData[i], record.GetDateTime(i)); - break; - case SqlDbType.SmallInt: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Int16)); - SetInt16_Unchecked(sink, setters, i, record.GetInt16(i)); - break; - case SqlDbType.SmallMoney: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlMoney)); - SetSqlMoney_Checked(sink, setters, i, metaData[i], record.GetSqlMoney(i)); - break; - case SqlDbType.Text: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlChars)); - SetChars_FromRecord(sink, setters, i, metaData[i], record, 0); - break; - case SqlDbType.Timestamp: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlBytes)); - SetBytes_FromRecord(sink, setters, i, metaData[i], record, 0); - break; - case SqlDbType.TinyInt: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.Byte)); - SetByte_Unchecked(sink, setters, i, record.GetByte(i)); - break; - case SqlDbType.VarBinary: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlBytes)); - SetBytes_FromRecord(sink, setters, i, metaData[i], record, 0); - break; - case SqlDbType.VarChar: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.String)); - SetChars_FromRecord(sink, setters, i, metaData[i], record, 0); - break; - case SqlDbType.Xml: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlXml)); - SetSqlXml_Unchecked(sink, setters, i, record.GetSqlXml(i)); // perf improvement? - break; - case SqlDbType.Variant: - object o = record.GetSqlValue(i); - ExtendedClrTypeCode typeCode = MetaDataUtilsSmi.DetermineExtendedTypeCode(o); - SetCompatibleValue(sink, setters, i, metaData[i], o, typeCode, 0); - break; - case SqlDbType.Udt: - Debug.Assert(CanAccessSetterDirectly(metaData[i], ExtendedClrTypeCode.SqlBytes)); - SetBytes_FromRecord(sink, setters, i, metaData[i], record, 0); - break; - default: - Debug.Assert(false, "unsupported DbType:" + metaData[i].SqlDbType.ToString()); - throw ADP.NotSupported(); - } - } - } - } - - // spool a Stream into a scratch stream from the Smi interface and return it as a SqlStreamChars - internal static SqlStreamChars CopyIntoNewSmiScratchStreamChars(Stream source, SmiEventSink_Default sink, SmiContext context) - { - SqlClientWrapperSmiStreamChars dest = new(sink, context.GetScratchStream(sink)); - - int chunkSize; - if (source.CanSeek && source.Length < MaxByteChunkSize) - { - chunkSize = unchecked((int)source.Length); // unchecked cast is safe due to check on line above - } - else - { - chunkSize = MaxByteChunkSize; - } - - byte[] copyBuffer = new byte[chunkSize]; - int bytesRead; - while (0 != (bytesRead = source.Read(copyBuffer, 0, chunkSize))) - { - dest.Write(copyBuffer, 0, bytesRead); - } - dest.Flush(); - - // SQLBU 494334 - // Need to re-wind scratch stream to beginning before returning - dest.Seek(0, SeekOrigin.Begin); - - return dest; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiExecuteType.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionData.stub.cs similarity index 53% rename from src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiExecuteType.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionData.stub.cs index 70bb5b3b99..8911f38626 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/Server/SmiExecuteType.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionData.stub.cs @@ -2,15 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -namespace Microsoft.Data.SqlClient.Server -{ +// @TODO: This is only a stub class for removing clearing errors while merging other files. - // enum representing the type of execution requested - internal enum SmiExecuteType +namespace Microsoft.Data.SqlClient +{ + internal class SessionData { - NonQuery = 0, - Reader = 1, - ToPipe = 2, } } - diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SessionHandle.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionHandle.Windows.cs similarity index 62% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SessionHandle.Windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionHandle.Windows.cs index 3783256cc0..b5b2068961 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SessionHandle.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionHandle.Windows.cs @@ -14,15 +14,18 @@ namespace Microsoft.Data.SqlClient internal readonly ref struct SessionHandle { +#if NET public const int NativeHandleType = 1; public const int ManagedHandleType = 2; - public readonly SNI.SNIHandle ManagedHandle; - public readonly SNIHandle NativeHandle; - + public readonly ManagedSni.SniHandle ManagedHandle; public readonly int Type; +#endif + + public readonly SNIHandle NativeHandle; - public SessionHandle(SNI.SNIHandle managedHandle, SNIHandle nativeHandle, int type) +#if NET + public SessionHandle(ManagedSni.SniHandle managedHandle, SNIHandle nativeHandle, int type) { Type = type; ManagedHandle = managedHandle; @@ -31,8 +34,20 @@ public SessionHandle(SNI.SNIHandle managedHandle, SNIHandle nativeHandle, int ty public bool IsNull => (Type == NativeHandleType) ? NativeHandle is null : ManagedHandle is null; - public static SessionHandle FromManagedSession(SNI.SNIHandle managedSessionHandle) => new SessionHandle(managedSessionHandle, default, ManagedHandleType); + public static SessionHandle FromManagedSession(ManagedSni.SniHandle managedSessionHandle) => + new SessionHandle(managedSessionHandle, default, ManagedHandleType); + + public static SessionHandle FromNativeHandle(SNIHandle nativeSessionHandle) => + new SessionHandle(default, nativeSessionHandle, NativeHandleType); +#else + public SessionHandle(SNIHandle nativeHandle) + { + NativeHandle = nativeHandle; + } + + public bool IsNull => NativeHandle is null; - public static SessionHandle FromNativeHandle(SNIHandle nativeSessionHandle) => new SessionHandle(default, nativeSessionHandle, NativeHandleType); + public static SessionHandle FromNativeHandle(SNIHandle nativeSessionHandle) => new SessionHandle(nativeSessionHandle); +#endif } } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SessionHandle.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionHandle.netcore.Unix.cs similarity index 76% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SessionHandle.Unix.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionHandle.netcore.Unix.cs index 1ba0deafea..8465da3684 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SessionHandle.Unix.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SessionHandle.netcore.Unix.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + namespace Microsoft.Data.SqlClient { // this structure is used for transporting packet handle references between the TdsParserStateObject @@ -17,10 +19,10 @@ internal readonly ref struct SessionHandle public const int NativeHandleType = 1; public const int ManagedHandleType = 2; - public readonly SNI.SNIHandle ManagedHandle; + public readonly ManagedSni.SniHandle ManagedHandle; public readonly int Type; - public SessionHandle(SNI.SNIHandle managedHandle, int type) + public SessionHandle(ManagedSni.SniHandle managedHandle, int type) { Type = type; ManagedHandle = managedHandle; @@ -28,6 +30,9 @@ public SessionHandle(SNI.SNIHandle managedHandle, int type) public bool IsNull => ManagedHandle is null; - public static SessionHandle FromManagedSession(SNI.SNIHandle managedSessionHandle) => new SessionHandle(managedSessionHandle, ManagedHandleType); + public static SessionHandle FromManagedSession(ManagedSni.SniHandle managedSessionHandle) => + new SessionHandle(managedSessionHandle, ManagedHandleType); } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAppContextSwitchManager.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAppContextSwitchManager.netcore.cs similarity index 99% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAppContextSwitchManager.NetCoreApp.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAppContextSwitchManager.netcore.cs index 92a4e60487..e7d04e44bc 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlAppContextSwitchManager.NetCoreApp.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlAppContextSwitchManager.netcore.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; using System.Configuration; @@ -70,3 +72,5 @@ public string Value } } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatch.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatch.cs index 86d3e37094..7171bdbfe3 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatch.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBatch.cs @@ -4,6 +4,7 @@ #if NET +using System; using System.Collections.Generic; using System.Data; using System.Data.Common; @@ -223,6 +224,10 @@ private void SetupBatchCommandExecute() { throw ADP.ConnectionRequired(nameof(SetupBatchCommandExecute)); } + if (_commands is null) + { + throw ADP.InvalidOperation(StringsHelper.GetString(Strings.ADP_NoSqlBatchCommandList)); + } _batchCommand.Connection = Connection; _batchCommand.Transaction = Transaction; _batchCommand.SetBatchRPCMode(true, _commands.Count); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs index 779b290192..bf27581b51 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBuffer.cs @@ -11,7 +11,7 @@ namespace Microsoft.Data.SqlClient { - internal sealed partial class SqlBuffer + internal sealed class SqlBuffer { internal enum StorageType { @@ -37,6 +37,7 @@ internal enum StorageType DateTimeOffset, Time, Json, + Vector, } internal struct DateTimeInfo @@ -76,6 +77,12 @@ internal struct DateTimeOffsetInfo internal short _offset; } + internal struct VectorInfo + { + internal int _elementCount; + internal byte _elementType; + } + [StructLayout(LayoutKind.Explicit)] internal struct Storage { @@ -105,6 +112,8 @@ internal struct Storage internal DateTime2Info _dateTime2Info; [FieldOffset(0)] internal DateTimeOffsetInfo _dateTimeOffsetInfo; + [FieldOffset(0)] + internal VectorInfo _vectorInfo; } private bool _isNull; @@ -133,6 +142,15 @@ private SqlBuffer(SqlBuffer value) internal StorageType VariantInternalStorageType => _type; + internal Storage GetVectorInfo() + { + if (_type == StorageType.Vector) + { + return _value; + } + throw new InvalidOperationException(); + } + internal bool Boolean { get @@ -179,7 +197,10 @@ internal byte[] ByteArray { get { - ThrowIfNull(); + if (_type != StorageType.Vector) + { + ThrowIfNull(); + } return SqlBinary.Value; } } @@ -220,16 +241,16 @@ internal decimal Decimal // Only removing trailing zeros from a decimal part won't hit its value! if (_value._numericInfo._scale > 0) { - int zeroCnt = FindTrailingZerosAndPrec((uint)_value._numericInfo._data1, (uint)_value._numericInfo._data2, - (uint)_value._numericInfo._data3, (uint)_value._numericInfo._data4, + int zeroCnt = FindTrailingZerosAndPrec((uint)_value._numericInfo._data1, (uint)_value._numericInfo._data2, + (uint)_value._numericInfo._data3, (uint)_value._numericInfo._data4, _value._numericInfo._scale, out int precision); int minScale = _value._numericInfo._scale - zeroCnt; // minimum possible sacle after removing the trailing zeros. if (zeroCnt > 0 && minScale <= 28 && precision <= 29) { - SqlDecimal sqlValue = new(_value._numericInfo._precision, _value._numericInfo._scale, _value._numericInfo._positive, - _value._numericInfo._data1, _value._numericInfo._data2, + SqlDecimal sqlValue = new(_value._numericInfo._precision, _value._numericInfo._scale, _value._numericInfo._positive, + _value._numericInfo._data1, _value._numericInfo._data2, _value._numericInfo._data3, _value._numericInfo._data4); int integral = precision - minScale; @@ -486,7 +507,17 @@ internal string String get { ThrowIfNull(); - + if (_type == StorageType.Vector) + { + var elementType = (MetaType.SqlVectorElementType)_value._vectorInfo._elementType; + switch (elementType) + { + case MetaType.SqlVectorElementType.Float32: + return GetSqlVector().GetString(); + default: + throw SQL.VectorTypeNotSupported(elementType.ToString()); + } + } if (StorageType.String == _type || StorageType.Json == _type) { return (string)_object; @@ -654,7 +685,7 @@ internal SqlBinary SqlBinary { get { - if (StorageType.SqlBinary == _type) + if (_type is StorageType.SqlBinary or StorageType.Vector) { if (IsNull) { @@ -917,8 +948,23 @@ internal SqlString SqlString { get { + if (_type is StorageType.Vector) + { + if (IsNull) + { + return SqlString.Null; + } + var elementType = (MetaType.SqlVectorElementType)_value._vectorInfo._elementType; + switch (elementType) + { + case MetaType.SqlVectorElementType.Float32: + return new SqlString(GetSqlVector().GetString()); + default: + throw SQL.VectorTypeNotSupported(elementType.ToString()); + } + } // String and Json storage type are both strings. - if (StorageType.String == _type || StorageType.Json == _type) + if (_type is StorageType.String or StorageType.Json) { if (IsNull) { @@ -941,6 +987,19 @@ internal SqlString SqlString internal SqlJson SqlJson => (StorageType.Json == _type) ? (IsNull ? SqlTypes.SqlJson.Null : new SqlJson((string)_object)) : (SqlJson)SqlValue; + internal SqlVector GetSqlVector() where T : unmanaged + { + if (_type is StorageType.Vector) + { + if (IsNull) + { + return SqlVector.CreateNull(_value._vectorInfo._elementCount); + } + return new SqlVector(SqlBinary.Value); + } + return (SqlVector)SqlValue; + } + internal object SqlValue { get @@ -975,6 +1034,15 @@ internal object SqlValue return SqlString; case StorageType.Json: return SqlJson; + case StorageType.Vector: + var elementType = (MetaType.SqlVectorElementType)_value._vectorInfo._elementType; + switch (elementType) + { + case MetaType.SqlVectorElementType.Float32: + return GetSqlVector(); + default: + throw SQL.VectorTypeNotSupported(elementType.ToString()); + } case StorageType.SqlCachedBuffer: { SqlCachedBuffer data = (SqlCachedBuffer)(_object); @@ -1067,6 +1135,7 @@ internal object Value case StorageType.String: return String; case StorageType.SqlBinary: + case StorageType.Vector: return ByteArray; case StorageType.SqlCachedBuffer: { @@ -1134,6 +1203,7 @@ internal Type GetTypeFromStorageType(bool isSqlType) case StorageType.SqlCachedBuffer: return typeof(SqlString); case StorageType.SqlBinary: + case StorageType.Vector: return typeof(object); case StorageType.SqlGuid: return typeof(SqlGuid); @@ -1190,6 +1260,8 @@ internal Type GetTypeFromStorageType(bool isSqlType) return typeof(DateTimeOffset); case StorageType.Json: return typeof(string); + case StorageType.Vector: + return typeof(byte[]); #if NET case StorageType.Time: return typeof(TimeOnly); @@ -1238,6 +1310,25 @@ internal void Clear() _object = null; } + #if NETFRAMEWORK + internal void SetToDate(DateTime date) + { + Debug.Assert(IsEmpty, "setting value a second time?"); + + _type = StorageType.Date; + _value._int32 = date.Subtract(DateTime.MinValue).Days; + _isNull = false; + } +#endif + + internal void SetVectorInfo(int elementCount, byte elementType, bool isNull) + { + _value._vectorInfo._elementCount = elementCount; + _value._vectorInfo._elementType = elementType; + _type = StorageType.Vector; + _isNull = isNull; + } + internal void SetToDateTime(int daypart, int timepart) { Debug.Assert(IsEmpty, "setting value a second time?"); @@ -1247,6 +1338,19 @@ internal void SetToDateTime(int daypart, int timepart) _isNull = false; } +#if NETFRAMEWORK + internal void SetToDateTime2(DateTime dateTime, byte scale) + { + Debug.Assert(IsEmpty, "setting value a second time?"); + + _type = StorageType.DateTime2; + _value._dateTime2Info._timeInfo._ticks = dateTime.TimeOfDay.Ticks; + _value._dateTime2Info._timeInfo._scale = scale; + _value._dateTime2Info._date = dateTime.Subtract(DateTime.MinValue).Days; + _isNull = false; + } +#endif + internal void SetToDecimal(byte precision, byte scale, bool positive, int[] bits) { Debug.Assert(IsEmpty, "setting value a second time?"); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs similarity index 84% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs index d368a25d50..7819e31bfe 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlBulkCopy.cs @@ -109,7 +109,7 @@ internal object[] CreateRowBuffer() } } - /// + /// public sealed class SqlBulkCopy : IDisposable { private enum ValueSourceType @@ -162,7 +162,7 @@ public SourceColumnMetadata(ValueMethod method, bool isSqlType, bool isDataFeed) private const int DefaultCommandTimeout = 30; - /// + /// public event SqlRowsCopiedEventHandler SqlRowsCopied; private bool _enableStreaming = false; @@ -243,7 +243,7 @@ internal static bool SetAlwaysTaskOnWrite } #endif - /// + /// public SqlBulkCopy(SqlConnection connection) { if (connection == null) @@ -255,7 +255,7 @@ public SqlBulkCopy(SqlConnection connection) ColumnOrderHints = new SqlBulkCopyColumnOrderHintCollection(); } - /// + /// public SqlBulkCopy(SqlConnection connection, SqlBulkCopyOptions copyOptions, SqlTransaction externalTransaction) : this(connection) { @@ -271,7 +271,7 @@ public SqlBulkCopy(SqlConnection connection, SqlBulkCopyOptions copyOptions, Sql } } - /// + /// public SqlBulkCopy(string connectionString) { if (connectionString == null) @@ -284,14 +284,14 @@ public SqlBulkCopy(string connectionString) _ownConnection = true; } - /// + /// public SqlBulkCopy(string connectionString, SqlBulkCopyOptions copyOptions) : this(connectionString) { _copyOptions = copyOptions; } - /// + /// public int BatchSize { get => _batchSize; @@ -308,7 +308,7 @@ public int BatchSize } } - /// + /// public int BulkCopyTimeout { get => _timeout; @@ -322,23 +322,23 @@ public int BulkCopyTimeout } } - /// + /// public bool EnableStreaming { get => _enableStreaming; set => _enableStreaming = value; } - /// + /// public SqlBulkCopyColumnMappingCollection ColumnMappings => _columnMappings; - /// + /// public SqlBulkCopyColumnOrderHintCollection ColumnOrderHints { get; } - /// + /// public string DestinationTableName { get => _destinationTableName; @@ -356,7 +356,7 @@ public string DestinationTableName } } - /// + /// public int NotifyAfter { get => _notifyAfter; @@ -375,10 +375,10 @@ public int NotifyAfter internal int ObjectID => _objectID; - /// + /// public int RowsCopied => unchecked((int)_rowsCopied); - /// + /// public long RowsCopied64 => _rowsCopied; internal SqlStatistics Statistics @@ -532,188 +532,231 @@ private string AnalyzeTargetAndCreateUpdateBulkCommand(BulkCopySimpleResultSet i string[] parts = MultipartIdentifier.ParseMultipartIdentifier(DestinationTableName, "[\"", "]\"", Strings.SQL_BulkCopyDestinationTableName, true); updateBulkCommandText.AppendFormat("insert bulk {0} (", ADP.BuildMultiPartName(parts)); - int nmatched = 0; // Number of columns that match and are accepted - int nrejected = 0; // Number of columns that match but were rejected - bool rejectColumn; // True if a column is rejected because of an excluded type - bool isInTransaction; - - isInTransaction = _connection.HasLocalTransaction; // Throw if there is a transaction but no flag is set - if (isInTransaction && _externalTransaction == null && _internalTransaction == null && (_connection.Parser != null && _connection.Parser.CurrentTransaction != null && _connection.Parser.CurrentTransaction.IsLocal)) + if (_connection.HasLocalTransaction + && _externalTransaction == null + && _internalTransaction == null + && _connection.Parser != null + && _connection.Parser.CurrentTransaction != null + && _connection.Parser.CurrentTransaction.IsLocal) { throw SQL.BulkLoadExistingTransaction(); } HashSet destColumnNames = new HashSet(); - // Loop over the metadata for each column + // Keep track of any result columns that we don't have a local + // mapping for. + #if NETFRAMEWORK + HashSet unmatchedColumns = new(); + #else + HashSet unmatchedColumns = new(_localColumnMappings.Count); + #endif + + // Start by assuming all locally mapped Destination columns will be + // unmatched. + for (int i = 0; i < _localColumnMappings.Count; ++i) + { + unmatchedColumns.Add(_localColumnMappings[i].DestinationColumn); + } + + // Flag to remember whether or not we need to append a comma before + // the next column in the command text. + bool appendComma = false; + + // Loop over the metadata for each result column. _SqlMetaDataSet metaDataSet = internalResults[MetaDataResultId].MetaData; _sortedColumnMappings = new List<_ColumnMapping>(metaDataSet.Length); for (int i = 0; i < metaDataSet.Length; i++) { _SqlMetaData metadata = metaDataSet[i]; - rejectColumn = false; - // Check for excluded types - if ((metadata.type == SqlDbType.Timestamp) - || ((metadata.IsIdentity) && !IsCopyOption(SqlBulkCopyOptions.KeepIdentity))) + bool matched = false; + bool rejected = false; + + // Look for a local match for the remote column. + for (int j = 0; j < _localColumnMappings.Count; ++j) { - // Remove metadata for excluded columns - metaDataSet[i] = null; - rejectColumn = true; - // We still need to find a matching column association - } + var localColumn = _localColumnMappings[j]; - // Find out if this column is associated - int assocId; - for (assocId = 0; assocId < _localColumnMappings.Count; assocId++) - { - if ((_localColumnMappings[assocId]._destinationColumnOrdinal == metadata.ordinal) || - (UnquotedName(_localColumnMappings[assocId]._destinationColumnName) == metadata.column)) + // Are we missing a mapping between the result column and + // this local column (by ordinal or name)? + if (localColumn._destinationColumnOrdinal != metadata.ordinal + && UnquotedName(localColumn._destinationColumnName) != metadata.column) { - if (rejectColumn) - { - nrejected++; // Count matched columns only - break; - } + // Yes, so move on to the next local column. + continue; + } - _sortedColumnMappings.Add(new _ColumnMapping(_localColumnMappings[assocId]._internalSourceColumnOrdinal, metadata)); - destColumnNames.Add(metadata.column); - nmatched++; + // Ok, we found a matching local column. + matched = true; + + // Remove it from our unmatched set. + unmatchedColumns.Remove(localColumn.DestinationColumn); + + // Check for column types that we refuse to bulk load, even + // though we found a match. + // + // We will not process timestamp or identity columns. + // + if (metadata.type == SqlDbType.Timestamp + || (metadata.IsIdentity && !IsCopyOption(SqlBulkCopyOptions.KeepIdentity))) + { + rejected = true; + break; + } - if (nmatched > 1) - { - updateBulkCommandText.Append(", "); // A leading comma for all but the first one - } + _sortedColumnMappings.Add(new _ColumnMapping(localColumn._internalSourceColumnOrdinal, metadata)); + destColumnNames.Add(metadata.column); - // Some datatypes need special handling ... - if (metadata.type == SqlDbType.Variant) - { - AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "sql_variant"); - } - else if (metadata.type == SqlDbType.Udt) - { - AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "varbinary"); - } - else if (metadata.type == SqlDbTypeExtensions.Json) - { - AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "json"); - } - else - { - AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, metadata.type.ToString()); - } + // Append a comma for each subsequent column. + if (appendComma) + { + updateBulkCommandText.Append(", "); + } + else + { + appendComma = true; + } - switch (metadata.metaType.NullableType) - { - case TdsEnums.SQLNUMERICN: - case TdsEnums.SQLDECIMALN: - // Decimal and numeric need to include precision and scale - updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0},{1})", metadata.precision, metadata.scale); - break; - case TdsEnums.SQLUDT: + // Some datatypes need special handling ... + if (metadata.type == SqlDbType.Variant) + { + AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "sql_variant"); + } + else if (metadata.type == SqlDbType.Udt) + { + AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "varbinary"); + } + else if (metadata.type == SqlDbTypeExtensions.Json) + { + AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "json"); + } + else if (metadata.type == SqlDbTypeExtensions.Vector) + { + AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, "vector"); + } + else + { + AppendColumnNameAndTypeName(updateBulkCommandText, metadata.column, metadata.type.ToString()); + } + + switch (metadata.metaType.NullableType) + { + case TdsEnums.SQLNUMERICN: + case TdsEnums.SQLDECIMALN: + // Decimal and numeric need to include precision and scale + updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0},{1})", metadata.precision, metadata.scale); + break; + case TdsEnums.SQLUDT: + { + if (metadata.IsLargeUdt) { - if (metadata.IsLargeUdt) - { - updateBulkCommandText.Append("(max)"); - } - else - { - int size = metadata.length; - updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", size); - } - break; + updateBulkCommandText.Append("(max)"); + } + else + { + int size = metadata.length; + updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", size); } - case TdsEnums.SQLTIME: - case TdsEnums.SQLDATETIME2: - case TdsEnums.SQLDATETIMEOFFSET: - // date, dateime2, and datetimeoffset need to include scale - updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", metadata.scale); break; - default: + } + case TdsEnums.SQLTIME: + case TdsEnums.SQLDATETIME2: + case TdsEnums.SQLDATETIMEOFFSET: + // date, dateime2, and datetimeoffset need to include scale + updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", metadata.scale); + break; + default: + { + // For non-long non-fixed types we need to add the Size + if (!metadata.metaType.IsFixed && !metadata.metaType.IsLong) { - // For non-long non-fixed types we need to add the Size - if (!metadata.metaType.IsFixed && !metadata.metaType.IsLong) - { - int size = metadata.length; - switch (metadata.metaType.NullableType) - { - case TdsEnums.SQLNCHAR: - case TdsEnums.SQLNVARCHAR: - case TdsEnums.SQLNTEXT: - size /= 2; - break; - default: - break; - } - updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", size); - } - else if (metadata.metaType.IsPlp && metadata.metaType.SqlDbType != SqlDbType.Xml && metadata.metaType.SqlDbType != SqlDbTypeExtensions.Json) + int size = metadata.length; + switch (metadata.metaType.NullableType) { - // Partial length column prefix (max) - updateBulkCommandText.Append("(max)"); + case TdsEnums.SQLNCHAR: + case TdsEnums.SQLNVARCHAR: + case TdsEnums.SQLNTEXT: + size /= 2; + break; + case TdsEnums.SQLVECTOR: + size = MetaType.GetVectorElementCount(metadata.length, metadata.scale); + break; + default: + break; } - break; + updateBulkCommandText.AppendFormat((IFormatProvider)null, "({0})", size); } - } + else if (metadata.metaType.IsPlp && !(metadata.metaType.SqlDbType is SqlDbType.Xml or SqlDbTypeExtensions.Json or SqlDbTypeExtensions.Vector)) + { + // Partial length column prefix (max) + updateBulkCommandText.Append("(max)"); + } + break; + } + } - // Get collation for column i - Result rowset = internalResults[CollationResultId]; - object rowvalue = rowset[i][CollationId]; + // Get collation for column i + Result rowset = internalResults[CollationResultId]; + object rowvalue = rowset[i][CollationId]; - bool shouldSendCollation; - switch (metadata.type) - { - case SqlDbType.Char: - case SqlDbType.NChar: - case SqlDbType.VarChar: - case SqlDbType.NVarChar: - case SqlDbType.Text: - case SqlDbType.NText: - shouldSendCollation = true; - break; + bool shouldSendCollation; + switch (metadata.type) + { + case SqlDbType.Char: + case SqlDbType.NChar: + case SqlDbType.VarChar: + case SqlDbType.NVarChar: + case SqlDbType.Text: + case SqlDbType.NText: + shouldSendCollation = true; + break; - default: - shouldSendCollation = false; - break; - } + default: + shouldSendCollation = false; + break; + } + + if (rowvalue != null && shouldSendCollation) + { + Debug.Assert(rowvalue is SqlString); - if (rowvalue != null && shouldSendCollation) + SqlString collation_name = (SqlString)rowvalue; + if (!collation_name.IsNull) { - Debug.Assert(rowvalue is SqlString); - SqlString collation_name = (SqlString)rowvalue; - if (!collation_name.IsNull) + updateBulkCommandText.Append(" COLLATE " + collation_name.Value); + // Compare collations only if the collation value was set on the metadata + if (_sqlDataReaderRowSource != null && metadata.collation != null) { - updateBulkCommandText.Append(" COLLATE " + collation_name.Value); - // Compare collations only if the collation value was set on the metadata - if (_sqlDataReaderRowSource != null && metadata.collation != null) + // On SqlDataReader we can verify the sourcecolumn collation! + int sourceColumnId = localColumn._internalSourceColumnOrdinal; + int destinationLcid = metadata.collation.LCID; + int sourceLcid = _sqlDataReaderRowSource.GetLocaleId(sourceColumnId); + if (sourceLcid != destinationLcid) { - // On SqlDataReader we can verify the sourcecolumn collation! - int sourceColumnId = _localColumnMappings[assocId]._internalSourceColumnOrdinal; - int destinationLcid = metadata.collation.LCID; - int sourceLcid = _sqlDataReaderRowSource.GetLocaleId(sourceColumnId); - if (sourceLcid != destinationLcid) - { - throw SQL.BulkLoadLcidMismatch(sourceLcid, _sqlDataReaderRowSource.GetName(sourceColumnId), destinationLcid, metadata.column); - } + throw SQL.BulkLoadLcidMismatch(sourceLcid, _sqlDataReaderRowSource.GetName(sourceColumnId), destinationLcid, metadata.column); } } } - break; } + + // We found a match, so no need to keep looking. + break; } - if (assocId == _localColumnMappings.Count) + + // Remove metadata for unmatched and rejected columns. + if (! matched || rejected) { - // Remove metadata for unmatched columns metaDataSet[i] = null; } } - // All columnmappings should have matched up - if (nmatched + nrejected != _localColumnMappings.Count) + // Do we have any unmatched columns? + if (unmatchedColumns.Count > 0) { - throw (SQL.BulkLoadNonMatchingColumnMapping()); + throw SQL.BulkLoadNonMatchingColumnNames(unmatchedColumns); } updateBulkCommandText.Append(")"); @@ -827,7 +870,7 @@ private void WriteMetaData(BulkCopySimpleResultSet internalResults) // Terminates the bulk copy operation. // Must be called at the end of the bulk copy session. - /// + /// public void Close() { if (_insideRowsCopiedEvent) @@ -864,6 +907,7 @@ private void Dispose(bool disposing) { throw; } + ADP.TraceExceptionWithoutRethrow(e); } } finally @@ -1089,10 +1133,9 @@ private Task ReadFromRowSourceAsync(CancellationToken cts) return _dbDataReaderRowSource.ReadAsync(cts).ContinueWith( static (Task task, object state) => { - SqlBulkCopy sqlBulkCopy = (SqlBulkCopy)state; if (task.Status == TaskStatus.RanToCompletion) { - sqlBulkCopy._hasMoreRowToCopy = task.Result; + ((SqlBulkCopy)state)._hasMoreRowToCopy = task.Result; } return task; }, @@ -1217,7 +1260,7 @@ private SourceColumnMetadata GetColumnMetadata(int ordinal) } } // Check for data streams - else if ((_enableStreaming) && ((metadata.length == MAX_LENGTH) || metadata.type == SqlDbTypeExtensions.Json)) + else if (_enableStreaming && (metadata.length == MAX_LENGTH || metadata.metaType.SqlDbType == SqlDbTypeExtensions.Json)) { isSqlType = false; @@ -1233,7 +1276,7 @@ private SourceColumnMetadata GetColumnMetadata(int ordinal) method = ValueMethod.DataFeedStream; } // For text and XML there is memory gain from streaming on destination side even if reader is non-sequential - else if (((metadata.type == SqlDbType.VarChar) || (metadata.type == SqlDbType.NVarChar || metadata.type == SqlDbTypeExtensions.Json)) && (mtSource.IsCharType) && (mtSource.SqlDbType != SqlDbType.Xml)) + else if ((metadata.type is SqlDbType.VarChar or SqlDbType.NVarChar or SqlDbTypeExtensions.Json) && mtSource.IsCharType && mtSource.SqlDbType != SqlDbType.Xml) { isDataFeed = true; method = ValueMethod.DataFeedText; @@ -1334,7 +1377,11 @@ private void RunParserReliably(BulkCopySimpleResultSet bulkCopyHandler = null) internalConnection.ThreadHasParserLockForClose = true; try { + #if NETFRAMEWORK + _parser.RunReliably(RunBehavior.UntilDone, null, null, bulkCopyHandler, _stateObj); + #else _parser.Run(RunBehavior.UntilDone, null, null, bulkCopyHandler, _stateObj); + #endif } finally { @@ -1519,6 +1566,10 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re { throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), ADP.ParameterValueOutOfRange(sqlValue)); } + catch (Exception e) + { + throw SQL.BulkLoadCannotConvertValue(value.GetType(), mt, metadata.ordinal, RowNumber, metadata.isEncrypted, metadata.column, value.ToString(), e); + } } // Perf: It is more efficient to write a SqlDecimal than a decimal since we need to break it into its 'bits' when writing @@ -1548,6 +1599,7 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re case TdsEnums.SQLTIME: case TdsEnums.SQLDATETIME2: case TdsEnums.SQLDATETIMEOFFSET: + case TdsEnums.SQLVECTOR: mt = MetaType.GetMetaTypeFromSqlDbType(type.SqlDbType, false); value = SqlParameter.CoerceValue(value, mt, out coercedToDataFeed, out typeChanged, false); break; @@ -1589,7 +1641,7 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re // in byte[] form. if (!(value is byte[])) { - value = _connection.GetBytes(value); + value = _connection.GetBytes(value, out _, out _); typeChanged = true; } break; @@ -1628,9 +1680,13 @@ private object ConvertValue(object value, _SqlMetaData metadata, bool isNull, re } } - /// + /// public void WriteToServer(DbDataReader reader) { + #if NETFRAMEWORK + SqlConnection.ExecutePermission.Demand(); + #endif + if (reader == null) { throw new ArgumentNullException(nameof(reader)); @@ -1645,6 +1701,7 @@ public void WriteToServer(DbDataReader reader) try { statistics = SqlStatistics.StartTimer(Statistics); + ResetWriteToServerGlobalVariables(); _rowSource = reader; _dbDataReaderRowSource = reader; @@ -1659,9 +1716,13 @@ public void WriteToServer(DbDataReader reader) } } - /// + /// public void WriteToServer(IDataReader reader) { + #if NETFRAMEWORK + SqlConnection.ExecutePermission.Demand(); + #endif + if (reader == null) { throw new ArgumentNullException(nameof(reader)); @@ -1676,11 +1737,13 @@ public void WriteToServer(IDataReader reader) try { statistics = SqlStatistics.StartTimer(Statistics); + ResetWriteToServerGlobalVariables(); _rowSource = reader; _sqlDataReaderRowSource = _rowSource as SqlDataReader; _dbDataReaderRowSource = _rowSource as DbDataReader; _rowSourceType = ValueSourceType.IDataReader; + WriteRowSourceToServerAsync(reader.FieldCount, CancellationToken.None); //It returns null since _isAsyncBulkCopy = false; } finally @@ -1689,12 +1752,16 @@ public void WriteToServer(IDataReader reader) } } - /// + /// public void WriteToServer(DataTable table) => WriteToServer(table, 0); - /// + /// public void WriteToServer(DataTable table, DataRowState rowState) { + #if NETFRAMEWORK + SqlConnection.ExecutePermission.Demand(); + #endif + if (table == null) { throw new ArgumentNullException(nameof(table)); @@ -1724,9 +1791,13 @@ public void WriteToServer(DataTable table, DataRowState rowState) } } - /// + /// public void WriteToServer(DataRow[] rows) { + #if NETFRAMEWORK + SqlConnection.ExecutePermission.Demand(); + #endif + SqlStatistics statistics = Statistics; if (rows == null) @@ -1764,13 +1835,15 @@ public void WriteToServer(DataRow[] rows) } } - /// + /// public Task WriteToServerAsync(DataRow[] rows) => WriteToServerAsync(rows, CancellationToken.None); - /// + /// public Task WriteToServerAsync(DataRow[] rows, CancellationToken cancellationToken) { - Task resultTask = null; + #if NETFRAMEWORK + SqlConnection.ExecutePermission.Demand(); + #endif if (rows == null) { @@ -1786,12 +1859,13 @@ public Task WriteToServerAsync(DataRow[] rows, CancellationToken cancellationTok try { statistics = SqlStatistics.StartTimer(Statistics); + ResetWriteToServerGlobalVariables(); if (rows.Length == 0) { - return cancellationToken.IsCancellationRequested ? - Task.FromCanceled(cancellationToken) : - Task.CompletedTask; + return cancellationToken.IsCancellationRequested + ? Task.FromCanceled(cancellationToken) + : Task.CompletedTask; } DataTable table = rows[0].Table; @@ -1802,22 +1876,26 @@ public Task WriteToServerAsync(DataRow[] rows, CancellationToken cancellationTok _rowSourceType = ValueSourceType.RowArray; _rowEnumerator = rows.GetEnumerator(); _isAsyncBulkCopy = true; - resultTask = WriteRowSourceToServerAsync(table.Columns.Count, cancellationToken); // It returns Task since _isAsyncBulkCopy = true; + + // It returns Task since _isAsyncBulkCopy = true; + return WriteRowSourceToServerAsync(table.Columns.Count, cancellationToken); } finally { SqlStatistics.StopTimer(statistics); } - return resultTask; } - /// + /// public Task WriteToServerAsync(DbDataReader reader) => WriteToServerAsync(reader, CancellationToken.None); - /// + /// public Task WriteToServerAsync(DbDataReader reader, CancellationToken cancellationToken) { - Task resultTask = null; + #if NETFRAMEWORK + SqlConnection.ExecutePermission.Demand(); + #endif + if (reader == null) { throw new ArgumentNullException(nameof(reader)); @@ -1827,33 +1905,37 @@ public Task WriteToServerAsync(DbDataReader reader, CancellationToken cancellati { throw SQL.BulkLoadPendingOperation(); } - + SqlStatistics statistics = Statistics; try { statistics = SqlStatistics.StartTimer(Statistics); + ResetWriteToServerGlobalVariables(); _rowSource = reader; _sqlDataReaderRowSource = reader as SqlDataReader; _dbDataReaderRowSource = reader; _rowSourceType = ValueSourceType.DbDataReader; _isAsyncBulkCopy = true; - resultTask = WriteRowSourceToServerAsync(reader.FieldCount, cancellationToken); // It returns Task since _isAsyncBulkCopy = true; + + // It returns Task since _isAsyncBulkCopy = true; + return WriteRowSourceToServerAsync(reader.FieldCount, cancellationToken); } finally { SqlStatistics.StopTimer(statistics); } - return resultTask; } - /// + /// public Task WriteToServerAsync(IDataReader reader) => WriteToServerAsync(reader, CancellationToken.None); - /// + /// public Task WriteToServerAsync(IDataReader reader, CancellationToken cancellationToken) { - Task resultTask = null; + #if NETFRAMEWORK + SqlConnection.ExecutePermission.Demand(); + #endif if (reader == null) { @@ -1875,28 +1957,31 @@ public Task WriteToServerAsync(IDataReader reader, CancellationToken cancellatio _dbDataReaderRowSource = _rowSource as DbDataReader; _rowSourceType = ValueSourceType.IDataReader; _isAsyncBulkCopy = true; - resultTask = WriteRowSourceToServerAsync(reader.FieldCount, cancellationToken); // It returns Task since _isAsyncBulkCopy = true; + + // It returns Task since _isAsyncBulkCopy = true; + return WriteRowSourceToServerAsync(reader.FieldCount, cancellationToken); } finally { SqlStatistics.StopTimer(statistics); } - return resultTask; } - /// + /// public Task WriteToServerAsync(DataTable table) => WriteToServerAsync(table, 0, CancellationToken.None); - /// + /// public Task WriteToServerAsync(DataTable table, CancellationToken cancellationToken) => WriteToServerAsync(table, 0, cancellationToken); - /// + /// public Task WriteToServerAsync(DataTable table, DataRowState rowState) => WriteToServerAsync(table, rowState, CancellationToken.None); - /// + /// public Task WriteToServerAsync(DataTable table, DataRowState rowState, CancellationToken cancellationToken) { - Task resultTask = null; + #if NETFRAMEWORK + SqlConnection.ExecutePermission.Demand(); + #endif if (table == null) { @@ -1912,6 +1997,7 @@ public Task WriteToServerAsync(DataTable table, DataRowState rowState, Cancellat try { statistics = SqlStatistics.StartTimer(Statistics); + ResetWriteToServerGlobalVariables(); _rowStateToSkip = ((rowState == 0) || (rowState == DataRowState.Deleted)) ? DataRowState.Deleted : ~rowState | DataRowState.Deleted; _rowSource = table; @@ -1919,17 +2005,25 @@ public Task WriteToServerAsync(DataTable table, DataRowState rowState, Cancellat _rowSourceType = ValueSourceType.DataTable; _rowEnumerator = table.Rows.GetEnumerator(); _isAsyncBulkCopy = true; - resultTask = WriteRowSourceToServerAsync(table.Columns.Count, cancellationToken); // It returns Task since _isAsyncBulkCopy = true; + + // It returns Task since _isAsyncBulkCopy = true; + return WriteRowSourceToServerAsync(table.Columns.Count, cancellationToken); } finally { SqlStatistics.StopTimer(statistics); } - return resultTask; } private Task WriteRowSourceToServerAsync(int columnCount, CancellationToken ctoken) { + // If user's token is canceled, return a canceled task + if (ctoken.IsCancellationRequested) + { + Debug.Assert(_isAsyncBulkCopy, "Should not have a cancelled token for a synchronous bulk copy"); + return Task.FromCanceled(ctoken); + } + Task reconnectTask = _connection._currentReconnectionTask; if (reconnectTask != null && !reconnectTask.IsCompleted) { @@ -1961,28 +2055,36 @@ private Task WriteRowSourceToServerAsync(int columnCount, CancellationToken ctok bool finishedSynchronously = true; _isBulkCopyingInProgress = true; - + CreateOrValidateConnection(nameof(WriteToServer)); + SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection(); Debug.Assert(_parserLock == null, "Previous parser lock not cleaned"); _parserLock = internalConnection._parserLock; _parserLock.Wait(canReleaseFromAnyThread: _isAsyncBulkCopy); + TdsParser bestEffortCleanupTarget = null; + + #if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); + #endif try { + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); WriteRowSourceToServerCommon(columnCount); // This is common in both sync and async Task resultTask = WriteToServerInternalAsync(ctoken); // resultTask is null for sync, but Task for async. if (resultTask != null) { finishedSynchronously = false; return resultTask.ContinueWith( - static (Task t, object state) => + static (Task task, object state) => { SqlBulkCopy sqlBulkCopy = (SqlBulkCopy)state; try { - sqlBulkCopy.AbortTransaction(); // If there is one, on success transactions will be committed. + // If there is one, on success transactions will be committed. + sqlBulkCopy.AbortTransaction(); } finally { @@ -1997,8 +2099,8 @@ private Task WriteRowSourceToServerAsync(int columnCount, CancellationToken ctok sqlBulkCopy._parserLock = null; } } - return t; - }, + return task; + }, state: this, scheduler: TaskScheduler.Default ).Unwrap(); @@ -2018,6 +2120,11 @@ private Task WriteRowSourceToServerAsync(int columnCount, CancellationToken ctok catch (System.Threading.ThreadAbortException e) { _connection.Abort(e); + + #if NETFRAMEWORK + SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); + #endif + throw; } finally @@ -2278,8 +2385,9 @@ private void CopyColumnsAsyncSetupContinuation(TaskCompletionSource sour { source.SetResult(null); } - } - ); + }, + connectionToDoom: _connection.GetOpenTdsConnection() + ); } // The notification logic. @@ -2415,8 +2523,8 @@ private Task CopyRowsAsync(int rowsSoFar, int totalRows, CancellationToken cts, resultTask = source.Task; AsyncHelper.ContinueTaskWithState(readTask, source, this, - onSuccess: (object state) => ((SqlBulkCopy)state).CopyRowsAsync(i + 1, totalRows, cts, source) - + onSuccess: (object state) => ((SqlBulkCopy)state).CopyRowsAsync(i + 1, totalRows, cts, source), + connectionToDoom: _connection.GetOpenTdsConnection() ); return resultTask; // Associated task will be completed when all rows are copied to server/exception/cancelled. } @@ -2440,12 +2548,14 @@ private Task CopyRowsAsync(int rowsSoFar, int totalRows, CancellationToken cts, else { AsyncHelper.ContinueTaskWithState(readTask, source, sqlBulkCopy, - onSuccess: (object state2) => ((SqlBulkCopy)state2).CopyRowsAsync(i + 1, totalRows, cts, source) + onSuccess: (object state2) => ((SqlBulkCopy)state2).CopyRowsAsync(i + 1, totalRows, cts, source), + connectionToDoom: _connection.GetOpenTdsConnection() ); } - } - ); - return resultTask; + }, + connectionToDoom: _connection.GetOpenTdsConnection() + ); + return resultTask; } } @@ -2523,7 +2633,8 @@ private Task CopyBatchesAsync(BulkCopySimpleResultSet internalResults, string up // Continuation finished sync, recall into CopyBatchesAsync to continue sqlBulkCopy.CopyBatchesAsync(internalResults, updateBulkCommandText, cts, source); } - } + }, + connectionToDoom: _connection.GetOpenTdsConnection() ); return source.Task; } @@ -2590,7 +2701,8 @@ private Task CopyBatchesAsyncContinued(BulkCopySimpleResultSet internalResults, } }, onFailure: static (Exception _, object state) => ((SqlBulkCopy)state).CopyBatchesAsyncContinuedOnError(cleanupParser: false), - onCancellation: static (object state) => ((SqlBulkCopy)state).CopyBatchesAsyncContinuedOnError(cleanupParser: true) + onCancellation: static (object state) => ((SqlBulkCopy)state).CopyBatchesAsyncContinuedOnError(cleanupParser: true), + connectionToDoom: _connection.GetOpenTdsConnection() ); return source.Task; @@ -2656,7 +2768,8 @@ private Task CopyBatchesAsyncContinuedOnSuccess(BulkCopySimpleResultSet internal // Always call back into CopyBatchesAsync sqlBulkCopy.CopyBatchesAsync(internalResults, updateBulkCommandText, cts, source); }, - onFailure: static (Exception _, object state) => ((SqlBulkCopy)state).CopyBatchesAsyncContinuedOnError(cleanupParser: false) + onFailure: static (Exception _, object state) => ((SqlBulkCopy)state).CopyBatchesAsyncContinuedOnError(cleanupParser: false), + connectionToDoom: _connection.GetOpenTdsConnection() ); return source.Task; } @@ -2679,6 +2792,10 @@ private Task CopyBatchesAsyncContinuedOnSuccess(BulkCopySimpleResultSet internal private void CopyBatchesAsyncContinuedOnError(bool cleanupParser) { SqlInternalConnectionTds internalConnection = _connection.GetOpenTdsConnection(); + + #if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); + #endif try { if ((cleanupParser) && (_parser != null) && (_stateObj != null)) @@ -2819,7 +2936,8 @@ private void WriteToServerInternalRestContinuedAsync(BulkCopySimpleResultSet int } } } - } + }, + connectionToDoom: _connection.GetOpenTdsConnection() ); return; } @@ -2908,22 +3026,29 @@ private void WriteToServerInternalRestAsync(CancellationToken cts, TaskCompletio TaskCompletionSource cancellableReconnectTS = new TaskCompletionSource(); if (cts.CanBeCanceled) { - regReconnectCancel.Value = cts.Register(static (object tcs) => ((TaskCompletionSource)tcs).TrySetCanceled(), cancellableReconnectTS); + regReconnectCancel.Value = cts.Register( + static tcs => ((TaskCompletionSource)tcs).TrySetCanceled(), + cancellableReconnectTS); } - AsyncHelper.ContinueTaskWithState(reconnectTask, cancellableReconnectTS, + + AsyncHelper.ContinueTaskWithState( + reconnectTask, + cancellableReconnectTS, state: cancellableReconnectTS, - onSuccess: static (object state) => ((TaskCompletionSource)state).SetResult(null) - ); + onSuccess: static state => ((TaskCompletionSource)state).SetResult(null)); + // No need to cancel timer since SqlBulkCopy creates specific task source for reconnection. AsyncHelper.SetTimeoutExceptionWithState( completion: cancellableReconnectTS, timeout: BulkCopyTimeout, state: _destinationTableName, - onFailure: static (object state) => SQL.BulkLoadInvalidDestinationTable((string)state, SQL.CR_ReconnectTimeout()), + onFailure: static state => + SQL.BulkLoadInvalidDestinationTable((string)state, SQL.CR_ReconnectTimeout()), cancellationToken: CancellationToken.None ); + AsyncHelper.ContinueTaskWithState( - task: cancellableReconnectTS.Task, + task: cancellableReconnectTS.Task, completion: source, state: regReconnectCancel, onSuccess: (object state) => @@ -2938,9 +3063,15 @@ private void WriteToServerInternalRestAsync(CancellationToken cts, TaskCompletio _parserLock.Wait(canReleaseFromAnyThread: true); WriteToServerInternalRestAsync(cts, source); }, - onFailure: static (Exception _, object state) => ((StrongBox)state).Value.Dispose(), - onCancellation: static (object state) => ((StrongBox)state).Value.Dispose(), - exceptionConverter: (ex) => SQL.BulkLoadInvalidDestinationTable(_destinationTableName, ex)); + connectionToAbort: _connection, + onFailure: static (_, state) => ((StrongBox)state).Value.Dispose(), + onCancellation: static state => ((StrongBox)state).Value.Dispose(), + #if NET + exceptionConverter: ex => SQL.BulkLoadInvalidDestinationTable(_destinationTableName, ex) + #else + exceptionConverter: (ex, _) => SQL.BulkLoadInvalidDestinationTable(_destinationTableName, ex) + #endif + ); return; } else @@ -2989,7 +3120,8 @@ private void WriteToServerInternalRestAsync(CancellationToken cts, TaskCompletio if (internalResultsTask != null) { AsyncHelper.ContinueTaskWithState(internalResultsTask, source, this, - onSuccess: (object state) => ((SqlBulkCopy)state).WriteToServerInternalRestContinuedAsync(internalResultsTask.Result, cts, source) + onSuccess: (object state) => ((SqlBulkCopy)state).WriteToServerInternalRestContinuedAsync(internalResultsTask.Result, cts, source), + connectionToDoom: _connection.GetOpenTdsConnection() ); } else @@ -3020,9 +3152,7 @@ private Task WriteToServerInternalAsync(CancellationToken ctoken) if (_isAsyncBulkCopy) { source = new TaskCompletionSource(); // Creating the completion source/Task that we pass to application - resultTask = source.Task; - - resultTask = RegisterForConnectionCloseNotification(resultTask); + resultTask = RegisterForConnectionCloseNotification(source.Task); } if (_destinationTableName == null) @@ -3073,7 +3203,8 @@ private Task WriteToServerInternalAsync(CancellationToken ctoken) { sqlBulkCopy.WriteToServerInternalRestAsync(ctoken, source); // Passing the same completion which will be completed by the Callee. } - } + }, + connectionToDoom: _connection.GetOpenTdsConnection() ); return resultTask; } @@ -3091,7 +3222,7 @@ private Task WriteToServerInternalAsync(CancellationToken ctoken) } return resultTask; } - + private void ResetWriteToServerGlobalVariables() { _dataTableSource = null; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs index c794cb0d2a..75f87ea13c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientEventSource.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.Data.SqlClient.Diagnostics; using System; using System.Diagnostics.Tracing; using System.Text; @@ -9,78 +10,34 @@ namespace Microsoft.Data.SqlClient { - internal abstract class SqlClientEventSourceBase : EventSource - { - protected override void OnEventCommand(EventCommandEventArgs command) - { - base.OnEventCommand(command); - EventCommandMethodCall(command); - } - - protected virtual void EventCommandMethodCall(EventCommandEventArgs command) { } - - #region not implemented for .Net core 2.1, .Net standard 2.0 and lower - internal virtual void HardConnectRequest() { /*no-op*/ } - - internal virtual void HardDisconnectRequest() { /*no-op*/ } - - internal virtual void SoftConnectRequest() { /*no-op*/ } - - internal virtual void SoftDisconnectRequest() { /*no-op*/ } - - internal virtual void EnterNonPooledConnection() { /*no-op*/ } - - internal virtual void ExitNonPooledConnection() { /*no-op*/ } - - internal virtual void EnterPooledConnection() { /*no-op*/ } - - internal virtual void ExitPooledConnection() { /*no-op*/ } - - internal virtual void EnterActiveConnectionPoolGroup() { /*no-op*/ } - - internal virtual void ExitActiveConnectionPoolGroup() { /*no-op*/ } - - internal virtual void EnterInactiveConnectionPoolGroup() { /*no-op*/ } - - internal virtual void ExitInactiveConnectionPoolGroup() { /*no-op*/ } - - internal virtual void EnterActiveConnectionPool() { /*no-op*/ } - - internal virtual void ExitActiveConnectionPool() { /*no-op*/ } - - internal virtual void EnterInactiveConnectionPool() { /*no-op*/ } - - internal virtual void ExitInactiveConnectionPool() { /*no-op*/ } - - internal virtual void EnterActiveConnection() { /*no-op*/ } - - internal virtual void ExitActiveConnection() { /*no-op*/ } - - internal virtual void EnterFreeConnection() { /*no-op*/ } - - internal virtual void ExitFreeConnection() { /*no-op*/ } - - internal virtual void EnterStasisConnection() { /*no-op*/ } - - internal virtual void ExitStasisConnection() { /*no-op*/ } - - internal virtual void ReclaimedConnectionRequest() { /*no-op*/ } - #endregion - } - // Any changes to event writers might be considered as a breaking change. // Other libraries such as OpenTelemetry and ApplicationInsight have based part of their code on BeginExecute and EndExecute arguments number. [EventSource(Name = "Microsoft.Data.SqlClient.EventSource")] - internal partial class SqlClientEventSource : SqlClientEventSourceBase + internal partial class SqlClientEventSource : EventSource { // Defines the singleton instance for the Resources ETW provider - internal static readonly SqlClientEventSource Log = new(); + public static readonly SqlClientEventSource Log = new(); + + // Provides access to metrics. + public static readonly SqlClientMetrics Metrics = new SqlClientMetrics(Log); private SqlClientEventSource() { } private const string NullStr = "null"; private const string SqlCommand_ClassName = nameof(SqlCommand); +#if NET + protected override void OnEventCommand(EventCommandEventArgs command) + { + base.OnEventCommand(command); + + if (command.Command == EventCommand.Enable) + { + Metrics.EnableEventCounters(); + } + } +#endif + #region Event IDs // Initialized static Scope IDs private static long s_nextScopeId = 0; @@ -467,7 +424,7 @@ internal long TryScopeEnterEvent(string className, [System.Runtime.CompilerServi { StringBuilder sb = new StringBuilder(className); sb.Append(".").Append(memberName).Append(" | INFO | SCOPE | Entering Scope {0}"); - return SNIScopeEnter(sb.ToString()); + return ScopeEnter(sb.ToString()); } return 0; } @@ -936,6 +893,12 @@ internal void StateDumpEvent(string message, T0 args0, T1 args1) { StateDump(string.Format(message, args0?.ToString() ?? NullStr, args1?.ToString() ?? NullStr)); } + + [NonEvent] + internal void StateDumpEvent(string message, T0 args0, T1 args1, T2 args2) + { + StateDump(string.Format(message, args0?.ToString() ?? NullStr, args1?.ToString() ?? NullStr, args2?.ToString() ?? NullStr)); + } #endregion #region SNI Trace diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientFactory.cs index 4e1030f0d6..ca78a6d5b6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientFactory.cs @@ -2,28 +2,72 @@ // 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.Data.Common; using Microsoft.Data.Sql; + +#if NETFRAMEWORK using System; -using System.Data.Common; +using System.Reflection; using System.Security.Permissions; using System.Security; -using Microsoft.Data.Common; +#endif namespace Microsoft.Data.SqlClient { /// + #if NETFRAMEWORK + public sealed class SqlClientFactory : DbProviderFactory, IServiceProvider + #else public sealed class SqlClientFactory : DbProviderFactory -#if NETFRAMEWORK - , IServiceProvider -#endif + #endif { + #if NETFRAMEWORK + #region Constants / Member Variables + + private const string ExtensionAssemblyRef = + "System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=" + AssemblyRef.EcmaPublicKey; + + private const string MicrosoftDataSqlClientSqlProviderServicesTypeName = + "Microsoft.Data.SqlClient.SQLProviderServices, " + ExtensionAssemblyRef; + + private const string SystemDataCommonDbProviderServicesTypeName = + "System.Data.Common.DbProviderServices, " + ExtensionAssemblyRef; + private static readonly Lazy MicrosoftDataSqlClientProviderServicesInstance = + new(static () => + { + FieldInfo instanceFieldInfo = MicrosoftDataSqlClientSqlProviderServicesType.Value?.GetField( + "Instance", + BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Static); + return instanceFieldInfo?.GetValue(null); + }); + + private static readonly Lazy MicrosoftDataSqlClientSqlProviderServicesType = + new (static () => Type.GetType(MicrosoftDataSqlClientSqlProviderServicesTypeName, throwOnError: false)); + + private static readonly Lazy SystemDataCommonDbProviderServicesType = + new(static () => Type.GetType(SystemDataCommonDbProviderServicesTypeName, throwOnError: false)); + + #endregion + #endif + /// public static readonly SqlClientFactory Instance = new SqlClientFactory(); private SqlClientFactory() { } + + #if NET + /// + public override bool CanCreateBatch => true; + + /// + public override DbBatch CreateBatch() => new SqlBatch(); + + /// + public override DbBatchCommand CreateBatchCommand() => new SqlBatchCommand(); + #endif /// public override bool CanCreateDataSourceEnumerator => true; @@ -64,7 +108,7 @@ public override DbParameter CreateParameter() return new SqlParameter(); } -#if NETFRAMEWORK + #if NETFRAMEWORK /// public override CodeAccessPermission CreatePermission(PermissionState state) { @@ -72,37 +116,20 @@ public override CodeAccessPermission CreatePermission(PermissionState state) } /// - /// Extension mechanism for additional services; currently the only service - /// supported is the DbProviderServices + /// Extension mechanism for additional services; currently the only service supported is + /// the System.Data.Common.DbProviderServices type. /// - /// requested service provider or null. - object IServiceProvider.GetService(Type serviceType) - { - object result = null; - if (serviceType == GreenMethods.SystemDataCommonDbProviderServices_Type) - { - result = GreenMethods.MicrosoftDataSqlClientSqlProviderServices_Instance(); - } - return result; - } -#endif + /// Requested service provider or null. + object IServiceProvider.GetService(Type serviceType) => + serviceType == SystemDataCommonDbProviderServicesType.Value + ? MicrosoftDataSqlClientProviderServicesInstance.Value + : null; + #endif /// public override DbDataSourceEnumerator CreateDataSourceEnumerator() { return SqlDataSourceEnumerator.Instance; } - -#if NET - /// - public override bool CanCreateBatch => true; - - /// - public override DbBatch CreateBatch() => new SqlBatch(); - - /// - public override DbBatchCommand CreateBatchCommand() => new SqlBatchCommand(); -#endif - } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientPermission.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientPermission.netfx.cs new file mode 100644 index 0000000000..5c72383b8f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientPermission.netfx.cs @@ -0,0 +1,701 @@ +// 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. + +#if NETFRAMEWORK + +using System; +using System.Collections; +using System.Data; +using System.Diagnostics; +using System.Globalization; +using System.Net; +using System.Reflection; +using System.Security; +using System.Security.Permissions; +using Microsoft.Data.Common; +using DBDataPermission = System.Data.Common.DBDataPermission; + +namespace Microsoft.Data.SqlClient +{ + /// + [Serializable] + public sealed class SqlClientPermission : DBDataPermission + { + #region Member Variables + + // @TODO: Replace ArrayList with a typed list. + private NameValuePermission _keyvaluetree = NameValuePermission.Default; + private ArrayList _keyvalues; + + #endregion + + #region Constructors + + /// + [Obsolete("SqlClientPermission() has been deprecated. Use the SqlClientPermission(PermissionState.None) constructor. http://go.microsoft.com/fwlink/?linkid=14202", true)] // MDAC 86034 + public SqlClientPermission() : this(PermissionState.None) + { + } + + /// + public SqlClientPermission(PermissionState state) : base(state) + { + } + + /// + [Obsolete("SqlClientPermission(PermissionState state, Boolean allowBlankPassword) has been deprecated. Use the SqlClientPermission(PermissionState.None) constructor. http://go.microsoft.com/fwlink/?linkid=14202", true)] // MDAC 86034 + public SqlClientPermission(PermissionState state, bool allowBlankPassword) : this(state) + { + AllowBlankPassword = allowBlankPassword; + } + + internal SqlClientPermission(SqlClientPermissionAttribute permissionAttribute) : base(permissionAttribute) + { + // Used by SqlClientPermissionAttribute.CreatePermission + } + + internal SqlClientPermission(SqlConnectionString constr) : base(PermissionState.None) + { + // Used by SqlConnectionString.CreatePermissionSet + + if (constr != null) + { + AllowBlankPassword = constr.HasBlankPassword; // MDAC 84563 + AddPermissionEntry(new DbConnectionString(constr)); + } + + if (constr == null || constr.IsEmpty) + { + base.Add("", "", KeyRestrictionBehavior.AllowOnly); + } + } + + private SqlClientPermission(SqlClientPermission permission) : base(permission) + { + // for Copy + // Explicitly copyFrom to clone the key value pairs + CopyFrom(permission); + } + + #endregion + + #region Properties + + private bool _IsUnrestricted + { + get + { + return base.IsUnrestricted(); + } + + set + { + // Use Reflection to access the base class _isUnrestricted. There is no other way to alter this externally. + FieldInfo fieldInfo = GetType().BaseType.GetField( + "_isUnrestricted", + BindingFlags.Instance | BindingFlags.NonPublic); + fieldInfo.SetValue(this, value); + } + } + + #endregion + + #region Public/Internal Methods + + /// + public override void Add(string connectionString, string restrictions, KeyRestrictionBehavior behavior) + { + DbConnectionString constr = new DbConnectionString(connectionString, restrictions, behavior, SqlConnectionString.GetParseSynonyms(), false); + AddPermissionEntry(constr); + } + + /// + public override IPermission Copy() + { + return new SqlClientPermission(this); + } + + /// + public override void FromXml(SecurityElement securityElement) + { + // Reads an XML tree like: + // + // + // + + // code derived from CodeAccessPermission.ValidateElement + if (securityElement == null) + { + throw ADP.ArgumentNull("securityElement"); + } + + string tag = securityElement.Tag; + if (!tag.Equals(XmlStr._Permission) && !tag.Equals(XmlStr._IPermission)) + { + throw ADP.NotAPermissionElement(); + } + + string version = securityElement.Attribute(XmlStr._Version); + if (version != null && !version.Equals(XmlStr._VersionNumber)) + { + throw ADP.InvalidXMLBadVersion(); + } + + string unrestrictedValue = securityElement.Attribute(XmlStr._Unrestricted); + _IsUnrestricted = unrestrictedValue != null && bool.Parse(unrestrictedValue); + + Clear(); // MDAC 83105 + if (!_IsUnrestricted) + { + string allowNull = securityElement.Attribute(XmlStr._AllowBlankPassword); + AllowBlankPassword = allowNull != null && bool.Parse(allowNull); + + ArrayList children = securityElement.Children; + if (children != null) + { + foreach (SecurityElement keyElement in children) + { + tag = keyElement.Tag; + if (XmlStr._add == tag || (tag != null && XmlStr._add == tag.ToLower(CultureInfo.InvariantCulture))) + { + string constr = keyElement.Attribute(XmlStr._ConnectionString); + string restrt = keyElement.Attribute(XmlStr._KeyRestrictions); + string behavr = keyElement.Attribute(XmlStr._KeyRestrictionBehavior); + + KeyRestrictionBehavior behavior = KeyRestrictionBehavior.AllowOnly; + if (behavr != null) + { + behavior = (KeyRestrictionBehavior)Enum.Parse(typeof(KeyRestrictionBehavior), behavr, true); + } + + constr = WebUtility.HtmlDecode(constr); + restrt = WebUtility.HtmlDecode(restrt); + Add(constr, restrt, behavior); + } + } + } + } + else + { + AllowBlankPassword = false; + } + } + + /// + public override IPermission Intersect(IPermission target) + { + // used during Deny actions + if (target == null) + { + return null; + } + if (target.GetType() != this.GetType()) + { + throw ADP.PermissionTypeMismatch(); + } + if (this.IsUnrestricted()) + { // MDAC 84803, NDPWhidbey 29121 + return target.Copy(); + } + + DBDataPermission operand = (DBDataPermission)target; + if (operand.IsUnrestricted()) + { // NDPWhidbey 29121 + return this.Copy(); + } + + SqlClientPermission newPermission = (SqlClientPermission)operand.Copy(); + newPermission.AllowBlankPassword &= AllowBlankPassword; + + if (_keyvalues != null && newPermission._keyvalues != null) + { + newPermission._keyvalues.Clear(); + + newPermission._keyvaluetree.Intersect(newPermission._keyvalues, _keyvaluetree); + } + else + { + // either target.Add or this.Add have not been called + // return a non-null object so IsSubset calls will fail + newPermission._keyvalues = null; + newPermission._keyvaluetree = null; + } + + if (newPermission.IsEmpty()) + { // no intersection, MDAC 86773 + newPermission = null; + } + return newPermission; + } + + /// + public override bool IsSubsetOf(IPermission target) + { + if (null == target) + { + return IsEmpty(); + } + if (target.GetType() != this.GetType()) + { + throw ADP.PermissionTypeMismatch(); + } + + SqlClientPermission superset = (target as SqlClientPermission); + + bool subset = superset.IsUnrestricted(); + if (!subset) + { + if (!IsUnrestricted() && + (!AllowBlankPassword || superset.AllowBlankPassword) && + (_keyvalues == null || superset._keyvaluetree != null)) + { + + subset = true; + if (_keyvalues != null) + { + foreach (DbConnectionString kventry in _keyvalues) + { + if (!superset._keyvaluetree.CheckValueForKeyPermit(kventry)) + { + subset = false; + break; + } + } + } + } + } + return subset; + } + + /// + public override SecurityElement ToXml() + { + // Generates an XML tree like: + // + // + // + // + // + + Type type = GetType(); + SecurityElement root = new SecurityElement(XmlStr._IPermission); + root.AddAttribute(XmlStr._class, type.AssemblyQualifiedName.Replace('\"', '\'')); + root.AddAttribute(XmlStr._Version, XmlStr._VersionNumber); + + if (IsUnrestricted()) + { + root.AddAttribute(XmlStr._Unrestricted, XmlStr._true); + } + else + { + root.AddAttribute(XmlStr._AllowBlankPassword, AllowBlankPassword.ToString(CultureInfo.InvariantCulture)); + + if (_keyvalues != null) + { + foreach (DbConnectionString value in _keyvalues) + { + SecurityElement valueElement = new SecurityElement(XmlStr._add); + string tmp; + + tmp = SecurityElement.Escape(value.ConnectionString); + if (!string.IsNullOrEmpty(tmp)) + { + valueElement.AddAttribute(XmlStr._ConnectionString, tmp); + } + + tmp = SecurityElement.Escape(value.Restrictions) ?? ""; + valueElement.AddAttribute(XmlStr._KeyRestrictions, tmp); + + tmp = value.Behavior.ToString(); + valueElement.AddAttribute(XmlStr._KeyRestrictionBehavior, tmp); + + root.AddChild(valueElement); + } + } + } + return root; + } + + /// + public override IPermission Union(IPermission target) + { + if (target == null) + { + return Copy(); + } + + if (target.GetType() != GetType()) + { + throw ADP.PermissionTypeMismatch(); + } + + if (IsUnrestricted()) + { + return Copy(); + } + + SqlClientPermission newPermission = (SqlClientPermission)target.Copy(); + if (!newPermission.IsUnrestricted()) + { + newPermission.AllowBlankPassword |= AllowBlankPassword; + + if (_keyvalues != null) + { + foreach (DbConnectionString entry in _keyvalues) + { + newPermission.AddPermissionEntry(entry); + } + } + } + + return newPermission.IsEmpty() ? null : newPermission; + } + + internal void AddPermissionEntry(DbConnectionString entry) + { + if (_keyvaluetree == null) + { + _keyvaluetree = new NameValuePermission(); + } + + if (_keyvalues == null) + { + _keyvalues = new ArrayList(); + } + + NameValuePermission.AddEntry(_keyvaluetree, _keyvalues, entry); + _IsUnrestricted = false; + } + + #endregion + + #region Private Methods + + // Modified CopyFrom to make sure that we copy the Name Value Pair + private void CopyFrom(SqlClientPermission permission) + { + if (!_IsUnrestricted) + { + if (permission._keyvalues != null) + { + _keyvalues = (ArrayList)permission._keyvalues.Clone(); + + if (permission._keyvaluetree != null) + { + _keyvaluetree = permission._keyvaluetree.CopyNameValue(); + } + } + } + } + + private bool IsEmpty() + { + ArrayList keyvalues = _keyvalues; + bool flag = !IsUnrestricted() && !AllowBlankPassword && (keyvalues == null || (0 == keyvalues.Count)); + return flag; + } + + #endregion + + [Serializable] + private sealed class NameValuePermission : IComparable + { + // Reused as both key and value nodes: + // Key nodes link to value nodes. + // Value nodes link to key nodes. + private readonly string _value; + + // value node with (_restrictions != null) are allowed to match connection strings + private DbConnectionString _entry; + + private NameValuePermission[] _tree; // with branches + + internal static readonly NameValuePermission Default = null; + + internal NameValuePermission() + { // root node + } + + private NameValuePermission(NameValuePermission permit) + { + // deep-copy + _value = permit._value; + _entry = permit._entry; + _tree = permit._tree; + if (_tree != null) + { + NameValuePermission[] tree = _tree.Clone() as NameValuePermission[]; + for (int i = 0; i < tree.Length; ++i) + { + if (tree[i] != null) + { // WebData 98488 + tree[i] = tree[i].CopyNameValue(); // deep copy + } + } + _tree = tree; + } + } + + private NameValuePermission(string keyword) + { + _value = keyword; + } + + private NameValuePermission(string value, DbConnectionString entry) + { + _value = value; + _entry = entry; + } + + int IComparable.CompareTo(object other) => + string.CompareOrdinal(_value, ((NameValuePermission)other)._value); + + internal static void AddEntry(NameValuePermission kvtree, ArrayList entries, DbConnectionString entry) + { + Debug.Assert(entry != null, "null DbConnectionString"); + + if (entry.KeyChain != null) + { + for (NameValuePair keychain = entry.KeyChain; keychain != null; keychain = keychain.Next) + { + NameValuePermission kv = kvtree.CheckKeyForValue(keychain.Name); + if (kv == null) + { + kv = new NameValuePermission(keychain.Name); + kvtree.Add(kv); // add directly into live tree + } + + kvtree = kv; + + kv = kvtree.CheckKeyForValue(keychain.Value); + if (kv == null) + { + DbConnectionString insertValue = keychain.Next != null ? null : entry; + kv = new NameValuePermission(keychain.Value, insertValue); + kvtree.Add(kv); // add directly into live tree + if (insertValue != null) + { + entries.Add(insertValue); + } + } + else if (keychain.Next == null) + { + // shorter chain potential + if (kv._entry != null) + { + Debug.Assert(entries.Contains(kv._entry), "entries doesn't contain entry"); + entries.Remove(kv._entry); + kv._entry = kv._entry.Intersect(entry); // union new restrictions into existing tree + } + else + { + kv._entry = entry; + } + entries.Add(kv._entry); + } + kvtree = kv; + } + } + else + { + // global restrictions + DbConnectionString kentry = kvtree._entry; + if (kentry != null) + { + Debug.Assert(entries.Contains(kentry), "entries doesn't contain entry"); + entries.Remove(kentry); + kvtree._entry = kentry.Intersect(entry); + } + else + { + kvtree._entry = entry; + } + + entries.Add(kvtree._entry); + } + } + + internal bool CheckValueForKeyPermit(DbConnectionString parsetable) + { + if (parsetable == null) + { + return false; + } + + bool hasMatch = false; + NameValuePermission[] keytree = _tree; // _tree won't mutate but Add will replace it + if (keytree != null) + { + hasMatch = parsetable.IsEmpty; // MDAC 86773 + if (!hasMatch) + { + // which key do we follow the key-value chain on + foreach (var permitKey in keytree) + { + if (permitKey != null) + { + string keyword = permitKey._value; + + Debug.Assert(permitKey._entry == null, "key member has no restrictions"); + if (parsetable.ContainsKey(keyword)) + { + string valueInQuestion = (string)parsetable[keyword]; + + // keyword is restricted to certain values + NameValuePermission permitValue = permitKey.CheckKeyForValue(valueInQuestion); + if (permitValue != null) + { + //value does match - continue the chain down that branch + if (permitValue.CheckValueForKeyPermit(parsetable)) + { + hasMatch = true; + // adding a break statement is tempting, but wrong + // user can safely extend their restrictions for current rule to include missing keyword + // i.e. Add("provider=sqloledb;integrated security=sspi", "data provider=", KeyRestrictionBehavior.AllowOnly); + // i.e. Add("data provider=msdatashape;provider=sqloledb;integrated security=sspi", "", KeyRestrictionBehavior.AllowOnly); + } + else + { // failed branch checking + return false; + } + } + else + { // value doesn't match to expected values - fail here + return false; + } + } + } + } + } + // partial chain match, either leaf-node by shorter chain or fail mid-chain if ( _restrictions == null) + } + + DbConnectionString entry = _entry; + if (entry != null) + { + // also checking !hasMatch is tempting, but wrong + // user can safely extend their restrictions for current rule to include missing keyword + // i.e. Add("provider=sqloledb;integrated security=sspi", "data provider=", KeyRestrictionBehavior.AllowOnly); + // i.e. Add("provider=sqloledb;", "integrated security=;", KeyRestrictionBehavior.AllowOnly); + hasMatch = entry.IsSupersetOf(parsetable); + } + + // mid-chain failure + return hasMatch; + } + + internal NameValuePermission CopyNameValue() + { + return new NameValuePermission(this); + } + + internal void Intersect(ArrayList entries, NameValuePermission target) + { + if (target == null) + { + _tree = null; + _entry = null; + } + else + { + if (_entry != null) + { + entries.Remove(_entry); + _entry = _entry.Intersect(target._entry); + entries.Add(_entry); + } + else if (target._entry != null) + { + _entry = target._entry.Intersect(null); + entries.Add(_entry); + } + + if (_tree != null) + { + int count = _tree.Length; + for (int i = 0; i < _tree.Length; ++i) + { + NameValuePermission kvtree = target.CheckKeyForValue(_tree[i]._value); + if (kvtree != null) + { // does target tree contain our value + _tree[i].Intersect(entries, kvtree); + } + else + { + _tree[i] = null; + --count; + } + } + if (0 == count) + { + _tree = null; + } + else if (count < _tree.Length) + { + NameValuePermission[] kvtree = new NameValuePermission[count]; + for (int i = 0, j = 0; i < _tree.Length; ++i) + { + if (_tree[i] != null) + { + kvtree[j++] = _tree[i]; + } + } + _tree = kvtree; + } + } + } + } + + private void Add(NameValuePermission permit) + { + NameValuePermission[] tree = _tree; + int length = tree != null ? tree.Length : 0; + NameValuePermission[] newtree = new NameValuePermission[1 + length]; + for (int i = 0; i < newtree.Length - 1; ++i) + { + newtree[i] = tree[i]; + } + newtree[length] = permit; + Array.Sort(newtree); + _tree = newtree; + } + + private NameValuePermission CheckKeyForValue(string keyInQuestion) + { + NameValuePermission[] valuetree = _tree; // _tree won't mutate but Add will replace it + if (valuetree != null) + { + foreach (var permitValue in valuetree) + { + if (string.Equals(keyInQuestion, permitValue._value, StringComparison.OrdinalIgnoreCase)) + { + return permitValue; + } + } + } + return null; + } + } + + private static class XmlStr + { + internal const string _class = "class"; + internal const string _IPermission = "IPermission"; + internal const string _Permission = "Permission"; + internal const string _Unrestricted = "Unrestricted"; + internal const string _AllowBlankPassword = "AllowBlankPassword"; + internal const string _true = "true"; + internal const string _Version = "version"; + internal const string _VersionNumber = "1"; + + internal const string _add = "add"; + + internal const string _ConnectionString = "ConnectionString"; + internal const string _KeyRestrictions = "KeyRestrictions"; + internal const string _KeyRestrictionBehavior = "KeyRestrictionBehavior"; + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientPermissionAttribute.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientPermissionAttribute.netfx.cs new file mode 100644 index 0000000000..d26def255e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlClientPermissionAttribute.netfx.cs @@ -0,0 +1,32 @@ +// 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. + +#if NETFRAMEWORK + +using System; +using System.Data.Common; +using System.Security; +using System.Security.Permissions; + +namespace Microsoft.Data.SqlClient +{ + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Assembly, AllowMultiple = true, Inherited = false)] + [Serializable] + public sealed class SqlClientPermissionAttribute : DBDataPermissionAttribute + { + /// + public SqlClientPermissionAttribute(SecurityAction action) : base(action) + { + } + + /// + public override IPermission CreatePermission() + { + return new SqlClientPermission(this); + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Windows.cs index b2dad9ed0f..9960447465 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Windows.cs @@ -474,6 +474,8 @@ private byte[] RSAEncrypt(byte[] plainText, X509Certificate2 certificate) Debug.Assert(certificate.HasPrivateKey, "Attempting to encrypt with cert without privatekey"); RSA rsa = certificate.GetRSAPublicKey(); + + // CodeQL [SM03796] Required for an external standard: Always Encrypted only supports encrypting column encryption keys with RSA_OAEP(SHA1) (https://learn.microsoft.com/en-us/sql/t-sql/statements/create-column-encryption-key-transact-sql?view=sql-server-ver16) return rsa.Encrypt(plainText, RSAEncryptionPadding.OaepSHA1); } @@ -490,6 +492,8 @@ private byte[] RSADecrypt(byte[] cipherText, X509Certificate2 certificate) Debug.Assert(certificate.HasPrivateKey, "Attempting to decrypt with cert without privatekey"); RSA rsa = certificate.GetRSAPrivateKey(); + + // CodeQL [SM03796] Required for an external standard: Always Encrypted only supports encrypting column encryption keys with RSA_OAEP(SHA1) (https://learn.microsoft.com/en-us/sql/t-sql/statements/create-column-encryption-key-transact-sql?view=sql-server-ver16) return rsa.Decrypt(cipherText, RSAEncryptionPadding.OaepSHA1); } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.netcore.Unix.cs similarity index 93% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Unix.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.netcore.Unix.cs index 93a7a5577c..598385851c 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.Unix.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCertificateStoreProvider.netcore.Unix.cs @@ -2,11 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; namespace Microsoft.Data.SqlClient { - /// + /// public class SqlColumnEncryptionCertificateStoreProvider : SqlColumnEncryptionKeyStoreProvider { /// @@ -66,3 +68,5 @@ public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool al } } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.netcore.Unix.cs similarity index 99% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Unix.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.netcore.Unix.cs index a2bafa35a7..038b43df2d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.Unix.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCngProvider.netcore.Unix.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; namespace Microsoft.Data.SqlClient @@ -76,3 +78,5 @@ public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool al } } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.netcore.Unix.cs similarity index 99% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Unix.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.netcore.Unix.cs index 657304842e..5fe9980788 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.Unix.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlColumnEncryptionCspProvider.netcore.Unix.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET + using System; namespace Microsoft.Data.SqlClient @@ -78,3 +80,5 @@ public override bool VerifyColumnMasterKeyMetadata(string masterKeyPath, bool al } } } + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.stub.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.stub.cs new file mode 100644 index 0000000000..bb7e537cf8 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommand.stub.cs @@ -0,0 +1,15 @@ +// 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. + +// @TODO: This is only a stub class for removing clearing errors while merging other files. + +namespace Microsoft.Data.SqlClient +{ + public class SqlCommand + { + public SqlConnection Connection { get; set; } + + internal SqlStatistics Statistics { get; set; } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommandBuilder.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommandBuilder.cs index 0f57a0e74f..51e71cc14b 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommandBuilder.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlCommandBuilder.cs @@ -254,33 +254,15 @@ public static void DeriveParameters(SqlCommand command) { throw ADP.ArgumentNull(nameof(command)); } -#if NETFRAMEWORK TdsParser bestEffortCleanupTarget = null; +#if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); #endif try { -#if NETFRAMEWORK -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(command.Connection); - RuntimeHelpers.PrepareConstrainedRegions(); - try { - tdsReliabilitySection.Start(); -#else - { -#endif // DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(command.Connection); -#endif // NETFRAMEWORK - command.DeriveParameters(); -#if NETFRAMEWORK - } -#if DEBUG - finally { - tdsReliabilitySection.Stop(); - } -#endif // DEBUG -#endif // NETFRAMEWORK + command.DeriveParameters(); } catch (OutOfMemoryException e) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.stub.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.stub.cs new file mode 100644 index 0000000000..d888c2e637 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnection.stub.cs @@ -0,0 +1,96 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// @TODO: This is only a stub class for clearing errors while merging other files. + +using System; +using System.Data.Common; +using System.Threading.Tasks; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; +using Microsoft.SqlServer.Server; + +namespace Microsoft.Data.SqlClient +{ + public class SqlConnection : DbConnection + { + internal bool _applyTransientFaultHandling = false; + internal Task _currentReconnectionTask = null; + internal SessionData _recoverySessionData = null; + + #region Constructors + + internal SqlConnection() {} + + internal SqlConnection(string connectionString) {} + + #endregion + + #region Properties + + internal Guid ClientConnectionId { get; set; } + + #if NETFRAMEWORK + internal static System.Security.CodeAccessPermission ExecutePermission { get; set; } + #endif + + internal bool HasLocalTransaction { get; set; } + + internal DbConnectionInternal InnerConnection { get; set; } + + internal bool Is2008OrNewer { get; set; } + + internal int ObjectID { get; set; } + + internal TdsParser Parser { get; set; } + + internal DbConnectionPoolGroup PoolGroup { get; set; } + + internal SqlStatistics Statistics { get; set; } + + internal bool StatisticsEnabled { get; set; } + + internal DbConnectionOptions UserConnectionOptions { get; set; } + + #endregion + + #region Methods + + internal void Abort(Exception e) { } + + internal void AddWeakReference(object value, int tag) { } + + internal SqlTransaction BeginTransaction() => + null; + + internal byte[] GetBytes(object o, out Format format, out int maxSize) + { + format = Format.Unknown; + maxSize = 0; + return null; + } + + internal SqlInternalConnectionTds GetOpenTdsConnection() => null; + + internal void PermissionDemand() { } + + internal Task RegisterForConnectionCloseNotification(Task outerTask, object value, int tag) => + Task.FromResult(default); + + internal void SetInnerConnectionEvent(DbConnectionInternal to) { } + + internal bool SetInnerConnectionFrom(DbConnectionInternal to, DbConnectionInternal from) => + false; + + internal void SetInnerConnectionTo(DbConnectionInternal to) { } + + internal void ValidateConnectionForExecute(string method, SqlCommand command) { } + + internal Task ValidateAndReconnect(Action beforeDisconnect, int timeout) => + null; + + #endregion + } +} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs similarity index 80% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index dfa10c3b07..ae4c7bc98d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -6,23 +6,29 @@ using System.Data.Common; using System.Diagnostics; using System.IO; +using System.Reflection; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; using Microsoft.Data.ProviderBase; +using Microsoft.Data.SqlClient.ConnectionPool; + +#if NET +using System.Runtime.Loader; +#endif namespace Microsoft.Data.SqlClient { - sealed internal partial class SqlConnectionFactory : DbConnectionFactory + sealed internal class SqlConnectionFactory : DbConnectionFactory { - - private const string _metaDataXml = "MetaDataXml"; - + public static readonly SqlConnectionFactory SingletonInstance = new SqlConnectionFactory(); + private SqlConnectionFactory() : base() { + #if NET SubscribeToAssemblyLoadContextUnload(); + #endif } - public static readonly SqlConnectionFactory SingletonInstance = new SqlConnectionFactory(); - override public DbProviderFactory ProviderFactory { get @@ -30,20 +36,21 @@ override public DbProviderFactory ProviderFactory return SqlClientFactory.Instance; } } - - override protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection) - { - return CreateConnection(options, poolKey, poolGroupProviderInfo, pool, owningConnection, userOptions: null); - } - - override protected DbConnectionInternal CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions) + + protected override DbConnectionInternal CreateConnection( + DbConnectionOptions options, + DbConnectionPoolKey poolKey, + DbConnectionPoolGroupProviderInfo poolGroupProviderInfo, + IDbConnectionPool pool, + DbConnection owningConnection, + DbConnectionOptions userOptions) { SqlConnectionString opt = (SqlConnectionString)options; SqlConnectionPoolKey key = (SqlConnectionPoolKey)poolKey; SessionData recoverySessionData = null; - SqlConnection sqlOwningConnection = (SqlConnection)owningConnection; - bool applyTransientFaultHandling = sqlOwningConnection != null ? sqlOwningConnection._applyTransientFaultHandling : false; + SqlConnection sqlOwningConnection = owningConnection as SqlConnection; + bool applyTransientFaultHandling = sqlOwningConnection?._applyTransientFaultHandling ?? false; SqlConnectionString userOpt = null; if (userOptions != null) @@ -63,9 +70,10 @@ override protected DbConnectionInternal CreateConnection(DbConnectionOptions opt bool redirectedUserInstance = false; DbConnectionPoolIdentity identity = null; - // Pass DbConnectionPoolIdentity to SqlInternalConnectionTds if using integrated security. + // Pass DbConnectionPoolIdentity to SqlInternalConnectionTds if using integrated security + // or active directory integrated security. // Used by notifications. - if (opt.IntegratedSecurity) + if (opt.IntegratedSecurity || opt.Authentication is SqlAuthenticationMethod.ActiveDirectoryIntegrated) { if (pool != null) { @@ -135,7 +143,21 @@ override protected DbConnectionInternal CreateConnection(DbConnectionOptions opt opt = new SqlConnectionString(opt, instanceName, userInstance: false, setEnlistValue: null); poolGroupProviderInfo = null; // null so we do not pass to constructor below... } - return new SqlInternalConnectionTds(identity, opt, key.Credential, poolGroupProviderInfo, "", null, redirectedUserInstance, userOpt, recoverySessionData, applyTransientFaultHandling: applyTransientFaultHandling, key.AccessToken, pool, key.AccessTokenCallback); + + return new SqlInternalConnectionTds( + identity, + opt, + key.Credential, + poolGroupProviderInfo, + newPassword: string.Empty, + newSecurePassword: null, + redirectedUserInstance, + userOpt, + recoverySessionData, + applyTransientFaultHandling, + key.AccessToken, + pool, + key.AccessTokenCallback); } protected override DbConnectionOptions CreateConnectionOptions(string connectionString, DbConnectionOptions previous) @@ -145,7 +167,7 @@ protected override DbConnectionOptions CreateConnectionOptions(string connection return result; } - override internal DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(DbConnectionOptions connectionOptions) + internal override DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo(DbConnectionOptions connectionOptions) { DbConnectionPoolProviderInfo providerInfo = null; @@ -157,7 +179,7 @@ override internal DbConnectionPoolProviderInfo CreateConnectionPoolProviderInfo( return providerInfo; } - override protected DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(DbConnectionOptions connectionOptions) + protected override DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions(DbConnectionOptions connectionOptions) { SqlConnectionString opt = (SqlConnectionString)connectionOptions; @@ -167,7 +189,7 @@ override protected DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions { // never pool context connections. int connectionTimeout = opt.ConnectTimeout; - if ((0 < connectionTimeout) && (connectionTimeout < int.MaxValue / 1000)) + if (connectionTimeout > 0 && connectionTimeout < int.MaxValue / 1000) { connectionTimeout *= 1000; } @@ -175,8 +197,9 @@ override protected DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions { connectionTimeout = int.MaxValue; } - - if (opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive || opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) + + if (opt.Authentication is SqlAuthenticationMethod.ActiveDirectoryInteractive + or SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow) { // interactive/device code flow mode will always have pool's CreateTimeout = 10 x ConnectTimeout. if (connectionTimeout >= Int32.MaxValue / 10) @@ -189,24 +212,24 @@ override protected DbConnectionPoolGroupOptions CreateConnectionPoolGroupOptions } SqlClientEventSource.Log.TryTraceEvent("SqlConnectionFactory.CreateConnectionPoolGroupOptions | Set connection pool CreateTimeout '{0}' when Authentication mode '{1}' is used.", connectionTimeout, opt.Authentication); } + poolingOptions = new DbConnectionPoolGroupOptions( - opt.IntegratedSecurity, - opt.MinPoolSize, - opt.MaxPoolSize, - connectionTimeout, - opt.LoadBalanceTimeout, - opt.Enlist); + opt.IntegratedSecurity || opt.Authentication is SqlAuthenticationMethod.ActiveDirectoryIntegrated, + opt.MinPoolSize, + opt.MaxPoolSize, + connectionTimeout, + opt.LoadBalanceTimeout, + opt.Enlist); } return poolingOptions; } - - override internal DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo(DbConnectionOptions connectionOptions) + internal override DbConnectionPoolGroupProviderInfo CreateConnectionPoolGroupProviderInfo( + DbConnectionOptions connectionOptions) { return new SqlConnectionPoolGroupProviderInfo((SqlConnectionString)connectionOptions); } - internal static SqlConnectionString FindSqlConnectionOptions(SqlConnectionPoolKey key) { SqlConnectionString connectionOptions = (SqlConnectionString)SingletonInstance.FindConnectionOptions(key); @@ -221,7 +244,7 @@ internal static SqlConnectionString FindSqlConnectionOptions(SqlConnectionPoolKe return connectionOptions; } - + // @TODO: All these methods seem redundant ... shouldn't we always have a SqlConnection? override internal DbConnectionPoolGroup GetConnectionPoolGroup(DbConnection connection) { SqlConnection c = (connection as SqlConnection); @@ -312,6 +335,7 @@ protected override DbMetaDataFactory CreateMetaDataFactory(DbConnectionInternal internalConnection.ServerVersion); } + #if NET private void Unload(object sender, EventArgs e) { try @@ -324,7 +348,17 @@ private void Unload(object sender, EventArgs e) } } - partial void SubscribeToAssemblyLoadContextUnload(); + private void SqlConnectionFactoryAssemblyLoadContext_Unloading(AssemblyLoadContext obj) + { + Unload(obj, EventArgs.Empty); + } + + private void SubscribeToAssemblyLoadContextUnload() + { + AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()).Unloading += + SqlConnectionFactoryAssemblyLoadContext_Unloading; + } + #endif } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs index 972e56981d..3816e74e49 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionString.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -14,6 +13,8 @@ using System.Security; using System.Security.Permissions; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; +using Microsoft.Data.SqlClient.LocalDb; namespace Microsoft.Data.SqlClient { @@ -23,6 +24,7 @@ internal sealed class SqlConnectionString : DbConnectionOptions // used by pooling classes so it is much easier to verify correctness // when not worried about the class being modified during execution + // @TODO: Remove this in favor of using DbConnectionStringDefaults?? internal static class DEFAULT { internal const ApplicationIntent ApplicationIntent = DbConnectionStringDefaults.ApplicationIntent; @@ -71,6 +73,7 @@ internal static class DEFAULT #endif // NETFRAMEWORK } + // @TODO: Remove in favor of DbConnectionStringKeywords // SqlConnection ConnectionString Options internal static class KEY { @@ -126,6 +129,7 @@ internal static class KEY #endif // NETFRAMEWORK } + // @TODO: Remove in favor DbConnectionStringSynonyms // Constant for the number of duplicate options in the connection string private static class SYNONYM { @@ -326,7 +330,7 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G _contextConnection = ConvertValueToBoolean(KEY.Context_Connection, DEFAULT.Context_Connection); _currentLanguage = ConvertValueToString(KEY.Current_Language, DEFAULT.Current_Language); _dataSource = ConvertValueToString(KEY.Data_Source, DEFAULT.Data_Source); - _localDBInstance = LocalDBAPI.GetLocalDbInstanceNameFromServerName(_dataSource); + _localDBInstance = LocalDbApi.GetLocalDbInstanceNameFromServerName(_dataSource); _failoverPartner = ConvertValueToString(KEY.FailoverPartner, DEFAULT.FailoverPartner); _initialCatalog = ConvertValueToString(KEY.Initial_Catalog, DEFAULT.Initial_Catalog); _password = ConvertValueToString(KEY.Password, DEFAULT.Password); @@ -396,12 +400,12 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G if (_networkLibrary != null) { // MDAC 83525 string networkLibrary = _networkLibrary.Trim().ToLower(CultureInfo.InvariantCulture); - Hashtable netlib = NetlibMapping(); + Dictionary netlib = NetlibMapping(); if (!netlib.ContainsKey(networkLibrary)) { throw ADP.InvalidConnectionOptionValue(KEY.Network_Library); } - _networkLibrary = (string)netlib[networkLibrary]; + _networkLibrary = netlib[networkLibrary]; } else { @@ -449,12 +453,7 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G } // expand during construction so that CreatePermissionSet and Expand are consistent -#if NETFRAMEWORK - string datadir = null; - _expandedAttachDBFilename = ExpandDataDirectory(KEY.AttachDBFilename, _attachDBFileName, ref datadir); -#else _expandedAttachDBFilename = ExpandDataDirectory(KEY.AttachDBFilename, _attachDBFileName); -#endif // NETFRAMEWORK if (_expandedAttachDBFilename != null) { if (0 <= _expandedAttachDBFilename.IndexOf('|')) @@ -575,22 +574,22 @@ internal SqlConnectionString(string connectionString) : base(connectionString, G if (Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity && _hasPasswordKeyword) { - throw SQL.NonInteractiveWithPassword(DbConnectionStringBuilderUtil.ActiveDirectoryManagedIdentityString); + throw SQL.NonInteractiveWithPassword(DbConnectionStringUtilities.ActiveDirectoryManagedIdentityString); } if (Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI && _hasPasswordKeyword) { - throw SQL.NonInteractiveWithPassword(DbConnectionStringBuilderUtil.ActiveDirectoryMSIString); + throw SQL.NonInteractiveWithPassword(DbConnectionStringUtilities.ActiveDirectoryMSIString); } if (Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault && _hasPasswordKeyword) { - throw SQL.NonInteractiveWithPassword(DbConnectionStringBuilderUtil.ActiveDirectoryDefaultString); + throw SQL.NonInteractiveWithPassword(DbConnectionStringUtilities.ActiveDirectoryDefaultString); } if (Authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity && _hasPasswordKeyword) { - throw SQL.NonInteractiveWithPassword(DbConnectionStringBuilderUtil.ActiveDirectoryWorkloadIdentityString); + throw SQL.NonInteractiveWithPassword(DbConnectionStringUtilities.ActiveDirectoryWorkloadIdentityString); } } @@ -628,7 +627,7 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS _contextConnection = connectionOptions._contextConnection; _currentLanguage = connectionOptions._currentLanguage; _dataSource = dataSource; - _localDBInstance = LocalDBAPI.GetLocalDbInstanceNameFromServerName(_dataSource); + _localDBInstance = LocalDbApi.GetLocalDbInstanceNameFromServerName(_dataSource); _failoverPartner = connectionOptions._failoverPartner; _initialCatalog = connectionOptions._initialCatalog; _password = connectionOptions._password; @@ -693,9 +692,6 @@ internal SqlConnectionString(SqlConnectionString connectionOptions, string dataS internal ApplicationIntent ApplicationIntent => _applicationIntent; internal string ApplicationName => _applicationName; internal string AttachDBFilename => _attachDBFileName; - // Return a constant value rather than _contextConnection. This allows the JIT to trim - // the code paths referencing it. - internal bool ContextConnection => false; internal string CurrentLanguage => _currentLanguage; internal string DataSource => _dataSource; internal string LocalDBInstance => _localDBInstance; @@ -772,55 +768,51 @@ internal static Dictionary GetParseSynonyms() { int count = SqlConnectionStringBuilder.KeywordsCount + SynonymCount; -#if NET - count += SqlConnectionStringBuilder.DeprecatedKeywordsCount; -#endif synonyms = new Dictionary(count, StringComparer.OrdinalIgnoreCase) { { KEY.ApplicationIntent, KEY.ApplicationIntent }, { KEY.Application_Name, KEY.Application_Name }, { KEY.AttachDBFilename, KEY.AttachDBFilename }, - { KEY.PoolBlockingPeriod, KEY.PoolBlockingPeriod}, + { KEY.AttestationProtocol, KEY.AttestationProtocol}, + { KEY.Authentication, KEY.Authentication }, + { KEY.ColumnEncryptionSetting, KEY.ColumnEncryptionSetting }, { KEY.Command_Timeout, KEY.Command_Timeout }, + { KEY.Connect_Retry_Count, KEY.Connect_Retry_Count }, + { KEY.Connect_Retry_Interval, KEY.Connect_Retry_Interval }, { KEY.Connect_Timeout, KEY.Connect_Timeout }, - { KEY.Connection_Reset, KEY.Connection_Reset }, { KEY.Context_Connection, KEY.Context_Connection }, { KEY.Current_Language, KEY.Current_Language }, { KEY.Data_Source, KEY.Data_Source }, + { KEY.EnclaveAttestationUrl, KEY.EnclaveAttestationUrl }, { KEY.Encrypt, KEY.Encrypt }, { KEY.Enlist, KEY.Enlist }, { KEY.FailoverPartner, KEY.FailoverPartner }, + { KEY.Failover_Partner_SPN, KEY.Failover_Partner_SPN }, { KEY.HostNameInCertificate, KEY.HostNameInCertificate }, { KEY.ServerCertificate, KEY.ServerCertificate}, { KEY.Initial_Catalog, KEY.Initial_Catalog }, { KEY.Integrated_Security, KEY.Integrated_Security }, + { KEY.IPAddressPreference, KEY.IPAddressPreference }, { KEY.Load_Balance_Timeout, KEY.Load_Balance_Timeout }, { KEY.MARS, KEY.MARS }, { KEY.Max_Pool_Size, KEY.Max_Pool_Size }, { KEY.Min_Pool_Size, KEY.Min_Pool_Size }, { KEY.MultiSubnetFailover, KEY.MultiSubnetFailover }, - { KEY.Network_Library, KEY.Network_Library }, { KEY.Packet_Size, KEY.Packet_Size }, { KEY.Password, KEY.Password }, { KEY.Persist_Security_Info, KEY.Persist_Security_Info }, { KEY.Pooling, KEY.Pooling }, + { KEY.PoolBlockingPeriod, KEY.PoolBlockingPeriod }, { KEY.Replication, KEY.Replication }, + { KEY.Server_SPN, KEY.Server_SPN }, { KEY.TrustServerCertificate, KEY.TrustServerCertificate }, { KEY.TransactionBinding, KEY.TransactionBinding }, { KEY.Type_System_Version, KEY.Type_System_Version }, - { KEY.ColumnEncryptionSetting, KEY.ColumnEncryptionSetting }, - { KEY.EnclaveAttestationUrl, KEY.EnclaveAttestationUrl }, - { KEY.AttestationProtocol, KEY.AttestationProtocol}, { KEY.User_ID, KEY.User_ID }, { KEY.User_Instance, KEY.User_Instance }, { KEY.Workstation_Id, KEY.Workstation_Id }, - { KEY.Connect_Retry_Count, KEY.Connect_Retry_Count }, - { KEY.Connect_Retry_Interval, KEY.Connect_Retry_Interval }, - { KEY.Authentication, KEY.Authentication }, - { KEY.IPAddressPreference, KEY.IPAddressPreference }, - { KEY.Server_SPN, KEY.Server_SPN }, - { KEY.Failover_Partner_SPN, KEY.Failover_Partner_SPN }, + { SYNONYM.IPADDRESSPREFERENCE, KEY.IPAddressPreference }, { SYNONYM.APP, KEY.Application_Name }, { SYNONYM.APPLICATIONINTENT, KEY.ApplicationIntent }, { SYNONYM.EXTENDED_PROPERTIES, KEY.AttachDBFilename }, @@ -841,24 +833,25 @@ internal static Dictionary GetParseSynonyms() { SYNONYM.SERVER, KEY.Data_Source }, { SYNONYM.DATABASE, KEY.Initial_Catalog }, { SYNONYM.TRUSTED_CONNECTION, KEY.Integrated_Security }, + { SYNONYM.TRUSTSERVERCERTIFICATE, KEY.TrustServerCertificate }, { SYNONYM.Connection_Lifetime, KEY.Load_Balance_Timeout }, - { SYNONYM.NET, KEY.Network_Library }, - { SYNONYM.NETWORK, KEY.Network_Library }, { SYNONYM.Pwd, KEY.Password }, { SYNONYM.PERSISTSECURITYINFO, KEY.Persist_Security_Info }, - { SYNONYM.TRUSTSERVERCERTIFICATE, KEY.TrustServerCertificate }, { SYNONYM.UID, KEY.User_ID }, { SYNONYM.User, KEY.User_ID }, { SYNONYM.WSID, KEY.Workstation_Id }, { SYNONYM.ServerSPN, KEY.Server_SPN }, { SYNONYM.FailoverPartnerSPN, KEY.Failover_Partner_SPN }, #if NETFRAMEWORK + { KEY.Connection_Reset, KEY.Connection_Reset }, + { KEY.Network_Library, KEY.Network_Library }, { KEY.TransparentNetworkIPResolution, KEY.TransparentNetworkIPResolution }, + { SYNONYM.NET, KEY.Network_Library }, + { SYNONYM.NETWORK, KEY.Network_Library }, { SYNONYM.TRANSPARENTNETWORKIPRESOLUTION, KEY.TransparentNetworkIPResolution }, #endif // NETFRAMEWORK - { SYNONYM.IPADDRESSPREFERENCE, KEY.IPAddressPreference } }; - Debug.Assert(synonyms.Count == count, "incorrect initial ParseSynonyms size"); + Debug.Assert(synonyms.Count == count, $"incorrect initial ParseSynonyms size {count} v/s {synonyms.Count}"); Interlocked.CompareExchange(ref s_sqlClientSynonyms, synonyms, null); } return synonyms; @@ -944,7 +937,7 @@ internal ApplicationIntent ConvertValueToApplicationIntent() // wrap Format and Overflow exceptions with Argument one, to be consistent with rest of the keyword types (like int and bool) try { - return DbConnectionStringBuilderUtil.ConvertToApplicationIntent(KEY.ApplicationIntent, value); + return DbConnectionStringUtilities.ConvertToApplicationIntent(KEY.ApplicationIntent, value); } catch (FormatException e) { @@ -976,7 +969,7 @@ internal SqlAuthenticationMethod ConvertValueToAuthenticationType() try { - return DbConnectionStringBuilderUtil.ConvertToAuthenticationType(KEY.Authentication, value); + return DbConnectionStringUtilities.ConvertToAuthenticationType(KEY.Authentication, value); } catch (FormatException e) { @@ -1001,7 +994,7 @@ internal SqlConnectionColumnEncryptionSetting ConvertValueToColumnEncryptionSett try { - return DbConnectionStringBuilderUtil.ConvertToColumnEncryptionSetting(KEY.ColumnEncryptionSetting, value); + return DbConnectionStringUtilities.ConvertToColumnEncryptionSetting(KEY.ColumnEncryptionSetting, value); } catch (FormatException e) { @@ -1026,7 +1019,7 @@ internal SqlConnectionAttestationProtocol ConvertValueToAttestationProtocol() try { - return DbConnectionStringBuilderUtil.ConvertToAttestationProtocol(KEY.AttestationProtocol, value); + return AttestationProtocolUtilities.ConvertToAttestationProtocol(KEY.AttestationProtocol, value); } catch (FormatException e) { @@ -1051,7 +1044,7 @@ internal SqlConnectionIPAddressPreference ConvertValueToIPAddressPreference() try { - return DbConnectionStringBuilderUtil.ConvertToIPAddressPreference(KEY.IPAddressPreference, value); + return IpAddressPreferenceUtilities.ConvertToIPAddressPreference(KEY.IPAddressPreference, value); } catch (FormatException e) { @@ -1072,7 +1065,7 @@ internal PoolBlockingPeriod ConvertValueToPoolBlockingPeriod() try { - return DbConnectionStringBuilderUtil.ConvertToPoolBlockingPeriod(KEY.PoolBlockingPeriod, value); + return PoolBlockingUtilities.ConvertToPoolBlockingPeriod(KEY.PoolBlockingPeriod, value); } catch (Exception e) when (e is FormatException || e is OverflowException) { @@ -1089,7 +1082,7 @@ internal SqlConnectionEncryptOption ConvertValueToSqlConnectionEncrypt() try { - return DbConnectionStringBuilderUtil.ConvertToSqlConnectionEncryptOption(KEY.Encrypt, value); + return AttestationProtocolUtilities.ConvertToSqlConnectionEncryptOption(KEY.Encrypt, value); } catch (FormatException e) { @@ -1101,14 +1094,14 @@ internal SqlConnectionEncryptOption ConvertValueToSqlConnectionEncrypt() } } - static internal Hashtable NetlibMapping() + static internal Dictionary NetlibMapping() { const int NetLibCount = 8; - Hashtable hash = s_netlibMapping; + Dictionary hash = s_netlibMapping; if (hash == null) { - hash = new Hashtable(NetLibCount) + hash = new Dictionary(NetLibCount) { { NETLIB.TCPIP, TdsEnums.TCP }, { NETLIB.NamedPipes, TdsEnums.NP }, @@ -1150,7 +1143,7 @@ internal static class NETLIB internal const string VIA = "dbmsgnet"; } - private static Hashtable s_netlibMapping; + private static Dictionary s_netlibMapping; #if NETFRAMEWORK protected internal override PermissionSet CreatePermissionSet() @@ -1160,12 +1153,6 @@ protected internal override PermissionSet CreatePermissionSet() return permissionSet; } - internal SqlConnectionEncryptOption ConvertValueToEncrypt() - { - SqlConnectionEncryptOption defaultEncryptValue = !Parsetable.ContainsKey(KEY.Authentication) ? DEFAULT.Encrypt : SqlConnectionEncryptOption.Mandatory; - return ConvertValueToSqlConnectionEncrypt(); - } - private readonly bool _connectionReset; private readonly bool _transparentNetworkIPResolution; private readonly string _networkLibrary; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs index bffe8cba5e..6c3e5989f4 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlConnectionStringBuilder.cs @@ -15,6 +15,7 @@ using System.Globalization; using System.Reflection; using Microsoft.Data.Common; +using Microsoft.Data.Common.ConnectionString; namespace Microsoft.Data.SqlClient { @@ -131,8 +132,6 @@ private enum Keywords private bool _connectionReset = DbConnectionStringDefaults.ConnectionReset; private bool _transparentNetworkIPResolution = DbConnectionStringDefaults.TransparentNetworkIPResolution; private string _networkLibrary = DbConnectionStringDefaults.NetworkLibrary; -#else - internal const int DeprecatedKeywordsCount = 5; #endif #endregion //Fields @@ -276,34 +275,35 @@ private static Dictionary CreateKeywordsDictionary() Debug.Assert((KeywordsCount + SqlConnectionString.SynonymCount) == pairs.Count, "initial expected size is incorrect"); return pairs; } + + // @TODO These methods are completely unnecessary. - private static bool ConvertToBoolean(object value) => DbConnectionStringBuilderUtil.ConvertToBoolean(value); + private static bool ConvertToBoolean(object value) => DbConnectionStringUtilities.ConvertToBoolean(value); - private static int ConvertToInt32(object value) => DbConnectionStringBuilderUtil.ConvertToInt32(value); + private static int ConvertToInt32(object value) => DbConnectionStringUtilities.ConvertToInt32(value); - private static bool ConvertToIntegratedSecurity(object value) => DbConnectionStringBuilderUtil.ConvertToIntegratedSecurity(value); + private static bool ConvertToIntegratedSecurity(object value) => DbConnectionStringUtilities.ConvertToIntegratedSecurity(value); - private static SqlAuthenticationMethod ConvertToAuthenticationType(string keyword, object value) => DbConnectionStringBuilderUtil.ConvertToAuthenticationType(keyword, value); + private static SqlAuthenticationMethod ConvertToAuthenticationType(string keyword, object value) => DbConnectionStringUtilities.ConvertToAuthenticationType(keyword, value); - private static string ConvertToString(object value) => DbConnectionStringBuilderUtil.ConvertToString(value); + private static string ConvertToString(object value) => DbConnectionStringUtilities.ConvertToString(value); - private static ApplicationIntent ConvertToApplicationIntent(string keyword, object value) => DbConnectionStringBuilderUtil.ConvertToApplicationIntent(keyword, value); + private static ApplicationIntent ConvertToApplicationIntent(string keyword, object value) => DbConnectionStringUtilities.ConvertToApplicationIntent(keyword, value); private static SqlConnectionColumnEncryptionSetting ConvertToColumnEncryptionSetting(string keyword, object value) - => DbConnectionStringBuilderUtil.ConvertToColumnEncryptionSetting(keyword, value); + => DbConnectionStringUtilities.ConvertToColumnEncryptionSetting(keyword, value); private static SqlConnectionAttestationProtocol ConvertToAttestationProtocol(string keyword, object value) - => DbConnectionStringBuilderUtil.ConvertToAttestationProtocol(keyword, value); + => AttestationProtocolUtilities.ConvertToAttestationProtocol(keyword, value); private static SqlConnectionEncryptOption ConvertToSqlConnectionEncryptOption(string keyword, object value) - => DbConnectionStringBuilderUtil.ConvertToSqlConnectionEncryptOption(keyword, value); - + => AttestationProtocolUtilities.ConvertToSqlConnectionEncryptOption(keyword, value); private static SqlConnectionIPAddressPreference ConvertToIPAddressPreference(string keyword, object value) - => DbConnectionStringBuilderUtil.ConvertToIPAddressPreference(keyword, value); + => IpAddressPreferenceUtilities.ConvertToIPAddressPreference(keyword, value); private static PoolBlockingPeriod ConvertToPoolBlockingPeriod(string keyword, object value) - => DbConnectionStringBuilderUtil.ConvertToPoolBlockingPeriod(keyword, value); + => PoolBlockingUtilities.ConvertToPoolBlockingPeriod(keyword, value); private object GetAt(Keywords index) { @@ -561,6 +561,8 @@ private void Reset(Keywords index) } } + // @TODO: These methods can be inlined with the property setters. + private void SetValue(string keyword, bool value) => base[keyword] = value.ToString(); private void SetValue(string keyword, int value) => base[keyword] = value.ToString((System.IFormatProvider)null); @@ -573,20 +575,20 @@ private void SetValue(string keyword, string value) private void SetApplicationIntentValue(ApplicationIntent value) { - Debug.Assert(DbConnectionStringBuilderUtil.IsValidApplicationIntentValue(value), "invalid value for ApplicationIntent"); - base[DbConnectionStringKeywords.ApplicationIntent] = DbConnectionStringBuilderUtil.ApplicationIntentToString(value); + Debug.Assert(DbConnectionStringUtilities.IsValidApplicationIntentValue(value), "invalid value for ApplicationIntent"); + base[DbConnectionStringKeywords.ApplicationIntent] = DbConnectionStringUtilities.ApplicationIntentToString(value); } private void SetColumnEncryptionSettingValue(SqlConnectionColumnEncryptionSetting value) { - Debug.Assert(DbConnectionStringBuilderUtil.IsValidColumnEncryptionSetting(value), "Invalid value for SqlConnectionColumnEncryptionSetting"); - base[DbConnectionStringKeywords.ColumnEncryptionSetting] = DbConnectionStringBuilderUtil.ColumnEncryptionSettingToString(value); + Debug.Assert(DbConnectionStringUtilities.IsValidColumnEncryptionSetting(value), "Invalid value for SqlConnectionColumnEncryptionSetting"); + base[DbConnectionStringKeywords.ColumnEncryptionSetting] = DbConnectionStringUtilities.ColumnEncryptionSettingToString(value); } private void SetAttestationProtocolValue(SqlConnectionAttestationProtocol value) { - Debug.Assert(DbConnectionStringBuilderUtil.IsValidAttestationProtocol(value), "Invalid value for SqlConnectionAttestationProtocol"); - base[DbConnectionStringKeywords.AttestationProtocol] = DbConnectionStringBuilderUtil.AttestationProtocolToString(value); + Debug.Assert(AttestationProtocolUtilities.IsValidAttestationProtocol(value), "Invalid value for SqlConnectionAttestationProtocol"); + base[DbConnectionStringKeywords.AttestationProtocol] = AttestationProtocolUtilities.AttestationProtocolToString(value); } private void SetSqlConnectionEncryptionValue(SqlConnectionEncryptOption value) @@ -596,20 +598,20 @@ private void SetSqlConnectionEncryptionValue(SqlConnectionEncryptOption value) private void SetIPAddressPreferenceValue(SqlConnectionIPAddressPreference value) { - Debug.Assert(DbConnectionStringBuilderUtil.IsValidIPAddressPreference(value), "Invalid value for SqlConnectionIPAddressPreference"); - base[DbConnectionStringKeywords.IPAddressPreference] = DbConnectionStringBuilderUtil.IPAddressPreferenceToString(value); + Debug.Assert(IpAddressPreferenceUtilities.IsValidIPAddressPreference(value), "Invalid value for SqlConnectionIPAddressPreference"); + base[DbConnectionStringKeywords.IPAddressPreference] = IpAddressPreferenceUtilities.IPAddressPreferenceToString(value); } private void SetAuthenticationValue(SqlAuthenticationMethod value) { - Debug.Assert(DbConnectionStringBuilderUtil.IsValidAuthenticationTypeValue(value), "Invalid value for AuthenticationType"); - base[DbConnectionStringKeywords.Authentication] = DbConnectionStringBuilderUtil.AuthenticationTypeToString(value); + Debug.Assert(DbConnectionStringUtilities.IsValidAuthenticationTypeValue(value), "Invalid value for AuthenticationType"); + base[DbConnectionStringKeywords.Authentication] = DbConnectionStringUtilities.AuthenticationTypeToString(value); } private void SetPoolBlockingPeriodValue(PoolBlockingPeriod value) { - Debug.Assert(DbConnectionStringBuilderUtil.IsValidPoolBlockingPeriodValue(value), "Invalid value for PoolBlockingPeriod"); - base[DbConnectionStringKeywords.PoolBlockingPeriod] = DbConnectionStringBuilderUtil.PoolBlockingPeriodToString(value); + Debug.Assert(PoolBlockingUtilities.IsValidPoolBlockingPeriodValue(value), "Invalid value for PoolBlockingPeriod"); + base[DbConnectionStringKeywords.PoolBlockingPeriod] = PoolBlockingUtilities.PoolBlockingPeriodToString(value); } private Exception UnsupportedKeyword(string keyword) @@ -747,7 +749,6 @@ private InstanceDescriptor ConvertToInstanceDescriptor(SqlConnectionStringBuilde } } -#if NETFRAMEWORK private sealed class SqlDataSourceConverter : StringConverter { private StandardValuesCollection _standardValues; @@ -766,10 +767,8 @@ public override StandardValuesCollection GetStandardValues(ITypeDescriptorContex { // Get the sources rowset for the SQLOLEDB enumerator DataTable table = SqlClientFactory.Instance.CreateDataSourceEnumerator().GetDataSources(); - string ServerName = typeof(System.Data.Sql.SqlDataSourceEnumerator).GetField("ServerName", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null).ToString(); - string InstanceName = typeof(System.Data.Sql.SqlDataSourceEnumerator).GetField("InstanceName", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null).ToString(); - DataColumn serverName = table.Columns[ServerName]; - DataColumn instanceName = table.Columns[InstanceName]; + DataColumn serverName = table.Columns[Microsoft.Data.Sql.SqlDataSourceEnumeratorUtil.ServerNameCol]; + DataColumn instanceName = table.Columns[Microsoft.Data.Sql.SqlDataSourceEnumeratorUtil.InstanceNameCol]; DataRowCollection rows = table.Rows; string[] serverNames = new string[rows.Count]; @@ -887,7 +886,7 @@ public override StandardValuesCollection GetStandardValues(ITypeDescriptorContex return standardValues; } } -#else +#if NET private static readonly string[] s_notSupportedKeywords = { DbConnectionStringKeywords.ConnectionReset, DbConnectionStringKeywords.TransactionBinding, @@ -1093,7 +1092,7 @@ public ApplicationIntent ApplicationIntent get => _applicationIntent; set { - if (!DbConnectionStringBuilderUtil.IsValidApplicationIntentValue(value)) + if (!DbConnectionStringUtilities.IsValidApplicationIntentValue(value)) { throw ADP.InvalidEnumerationValue(typeof(ApplicationIntent), (int)value); } @@ -1192,9 +1191,7 @@ public string CurrentLanguage [ResCategory(StringsHelper.ResourceNames.DataCategory_Source)] [ResDescription(StringsHelper.ResourceNames.DbConnectionString_DataSource)] [RefreshProperties(RefreshProperties.All)] -#if NETFRAMEWORK - [TypeConverter(typeof(SqlDataSourceConverter))] -#endif + [TypeConverter("Microsoft.Data.SqlClient.SqlConnectionStringBuilder+SqlDataSourceConverter, Microsoft.Data.SqlClient")] public string DataSource { get => _dataSource; @@ -1276,7 +1273,7 @@ public SqlConnectionColumnEncryptionSetting ColumnEncryptionSetting get => _columnEncryptionSetting; set { - if (!DbConnectionStringBuilderUtil.IsValidColumnEncryptionSetting(value)) + if (!DbConnectionStringUtilities.IsValidColumnEncryptionSetting(value)) { throw ADP.InvalidEnumerationValue(typeof(SqlConnectionColumnEncryptionSetting), (int)value); } @@ -1311,7 +1308,7 @@ public SqlConnectionAttestationProtocol AttestationProtocol get => _attestationProtocol; set { - if (!DbConnectionStringBuilderUtil.IsValidAttestationProtocol(value)) + if (!AttestationProtocolUtilities.IsValidAttestationProtocol(value)) { throw ADP.InvalidEnumerationValue(typeof(SqlConnectionAttestationProtocol), (int)value); } @@ -1331,7 +1328,7 @@ public SqlConnectionIPAddressPreference IPAddressPreference get => _ipAddressPreference; set { - if (!DbConnectionStringBuilderUtil.IsValidIPAddressPreference(value)) + if (!IpAddressPreferenceUtilities.IsValidIPAddressPreference(value)) { throw ADP.InvalidEnumerationValue(typeof(SqlConnectionIPAddressPreference), (int)value); } @@ -1376,9 +1373,7 @@ public bool Enlist [ResCategory(StringsHelper.ResourceNames.DataCategory_Source)] [ResDescription(StringsHelper.ResourceNames.DbConnectionString_FailoverPartner)] [RefreshProperties(RefreshProperties.All)] -#if NETFRAMEWORK - [TypeConverter(typeof(SqlDataSourceConverter))] -#endif + [TypeConverter("Microsoft.Data.SqlClient.SqlConnectionStringBuilder+SqlDataSourceConverter, Microsoft.Data.SqlClient")] public string FailoverPartner { get => _failoverPartner; @@ -1445,7 +1440,7 @@ public SqlAuthenticationMethod Authentication get => _authentication; set { - if (!DbConnectionStringBuilderUtil.IsValidAuthenticationTypeValue(value)) + if (!DbConnectionStringUtilities.IsValidAuthenticationTypeValue(value)) { throw ADP.InvalidEnumerationValue(typeof(SqlAuthenticationMethod), (int)value); } @@ -1642,7 +1637,7 @@ public PoolBlockingPeriod PoolBlockingPeriod get => _poolBlockingPeriod; set { - if (!DbConnectionStringBuilderUtil.IsValidPoolBlockingPeriodValue(value)) + if (!PoolBlockingUtilities.IsValidPoolBlockingPeriodValue(value)) { throw ADP.InvalidEnumerationValue(typeof(PoolBlockingPeriod), (int)value); } diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs similarity index 86% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs index f0ae13e6cd..353dd0346f 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReader.cs @@ -10,7 +10,9 @@ using System.Data.Common; using System.Data.SqlTypes; using System.Diagnostics; +#if NET using System.Diagnostics.CodeAnalysis; +#endif using System.Globalization; using System.IO; using System.Reflection; @@ -28,7 +30,7 @@ namespace Microsoft.Data.SqlClient { - /// + /// public class SqlDataReader : DbDataReader, IDataReader, IDbColumnSchemaGenerator { internal enum ALTROWSTATUS @@ -75,9 +77,9 @@ internal class SharedState private FieldNameLookup _fieldNameLookup; private CommandBehavior _commandBehavior; - private static int s_objectTypeCount; // Eventsource counter + private static int s_objectTypeCount; // EventSource counter private static readonly ReadOnlyCollection s_emptySchema = new ReadOnlyCollection(Array.Empty()); - internal static readonly int ObjectID = Interlocked.Increment(ref s_objectTypeCount); + internal readonly int ObjectID = Interlocked.Increment(ref s_objectTypeCount); // metadata (no explicit table, use 'Table') private MultiPartTableName[] _tableNames = null; @@ -99,6 +101,10 @@ internal class SharedState internal SqlDataReader(SqlCommand command, CommandBehavior behavior) { +#if NETFRAMEWORK + SqlConnection.VerifyExecutePermission(); +#endif + _command = command; _commandBehavior = behavior; if (_command != null) @@ -138,7 +144,7 @@ internal SqlCommand Command } } - /// + /// protected SqlConnection Connection { get @@ -147,10 +153,10 @@ protected SqlConnection Connection } } - /// + /// public SensitivityClassification SensitivityClassification { get; internal set; } - /// + /// override public int Depth { get @@ -164,7 +170,7 @@ override public int Depth } } - /// + /// // fields/attributes collection override public int FieldCount { @@ -188,7 +194,7 @@ override public int FieldCount } } - /// + /// override public bool HasRows { get @@ -206,7 +212,7 @@ override public bool HasRows } } - /// + /// override public bool IsClosed { get @@ -261,29 +267,43 @@ internal _SqlMetaDataSet MetaData #if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); #endif -#if NETFRAMEWORK && DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); try { - tdsReliabilitySection.Start(); -#else - { -#endif Debug.Assert(_stateObj == null || _stateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); if (TryConsumeMetaData() != TdsOperationStatus.Done) { throw SQL.SynchronousCallMayNotPend(); } } -#if NETFRAMEWORK && DEBUG - finally + catch (System.OutOfMemoryException e) { - tdsReliabilitySection.Stop(); + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; + } + catch (System.StackOverflowException e) + { + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; + } + catch (System.Threading.ThreadAbortException e) + { + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; } -#endif } + return _metaData; } } @@ -372,7 +392,7 @@ internal virtual SmiExtendedMetaData[] GetInternalSmiMetaData() return metaDataReturn; } - /// + /// override public int RecordsAffected { get @@ -415,7 +435,7 @@ internal MultiPartTableName[] TableNames } } - /// + /// override public int VisibleFieldCount { get @@ -433,7 +453,7 @@ override public int VisibleFieldCount } } - /// + /// // this operator override public object this[int i] { @@ -443,7 +463,7 @@ override public object this[int i] } } - /// + /// override public object this[string name] { get @@ -630,7 +650,6 @@ internal DataTable BuildSchemaTable() } else if (col.type == SqlDbType.Xml) { // Additional metadata for Xml. - Debug.Assert(Connection.Is2008OrNewer, "Invalid DataType (Xml) for the column"); schemaRow[xmlSchemaCollectionDatabase] = col.xmlSchemaCollection?.Database; schemaRow[xmlSchemaCollectionOwningSchema] = col.xmlSchemaCollection?.OwningSchema; schemaRow[xmlSchemaCollectionName] = col.xmlSchemaCollection?.Name; @@ -860,29 +879,42 @@ private void CleanPartialReadReliable() #if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); #endif -#if NETFRAMEWORK && DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); try { - tdsReliabilitySection.Start(); -#else - { -#endif TdsOperationStatus result = TryCleanPartialRead(); Debug.Assert(result == TdsOperationStatus.Done, "Should not pend on sync call"); Debug.Assert(!_sharedState._dataReady, "_dataReady should be cleared"); } -#if NETFRAMEWORK && DEBUG - finally + catch (System.OutOfMemoryException e) + { + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; + } + catch (System.StackOverflowException e) { - tdsReliabilitySection.Stop(); + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; + } + catch (System.Threading.ThreadAbortException e) + { + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; } -#endif } - /// + /// protected override void Dispose(bool disposing) { try @@ -899,7 +931,7 @@ protected override void Dispose(bool disposing) } } - /// + /// public override void Close() { using (TryEventScope.Create("SqlDataReader.Close | API | Object Id {0}, Command Object Id {1}", ObjectID, Command?.ObjectID)) @@ -1004,77 +1036,90 @@ private TdsOperationStatus TryCloseInternal(bool closeReader) #endif try { -#if NETFRAMEWORK && DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else + if ((!_isClosed) && (parser != null) && (stateObj != null) && (stateObj.HasPendingData)) { -#endif - if ((!_isClosed) && (parser != null) && (stateObj != null) && (stateObj.HasPendingData)) + // It is possible for this to be called during connection close on a + // broken connection, so check state first. + if (parser.State == TdsParserState.OpenLoggedIn) { - // It is possible for this to be called during connection close on a - // broken connection, so check state first. - if (parser.State == TdsParserState.OpenLoggedIn) - { - // if user called read but didn't fetch any values, skip the row - // same applies after NextResult on ALTROW because NextResult starts rowconsumption in that case ... + // if user called read but didn't fetch any values, skip the row + // same applies after NextResult on ALTROW because NextResult starts rowconsumption in that case ... - Debug.Assert(SniContext.Snix_Read == stateObj.SniContext, $"The SniContext should be Snix_Read but it actually is {stateObj.SniContext}"); + Debug.Assert(SniContext.Snix_Read == stateObj.SniContext, $"The SniContext should be Snix_Read but it actually is {stateObj.SniContext}"); - if (_altRowStatus == ALTROWSTATUS.AltRow) + if (_altRowStatus == ALTROWSTATUS.AltRow) + { + _sharedState._dataReady = true; // set _sharedState._dataReady to not confuse CleanPartialRead + } + _stateObj.SetTimeoutStateStopped(); + if (_sharedState._dataReady) + { + cleanDataFailed = true; + result = TryCleanPartialRead(); + if (result == TdsOperationStatus.Done) { - _sharedState._dataReady = true; // set _sharedState._dataReady to not confuse CleanPartialRead + cleanDataFailed = false; } - _stateObj.SetTimeoutStateStopped(); - if (_sharedState._dataReady) + else { - cleanDataFailed = true; - result = TryCleanPartialRead(); - if (result == TdsOperationStatus.Done) - { - cleanDataFailed = false; - } - else - { - return result; - } + return result; } + } #if DEBUG - else + else + { + byte token; + result = _stateObj.TryPeekByte(out token); + if (result != TdsOperationStatus.Done) { - byte token; - result = _stateObj.TryPeekByte(out token); - if (result != TdsOperationStatus.Done) - { - return result; - } - - Debug.Assert(TdsParser.IsValidTdsToken(token), $"DataReady is false, but next token is invalid: {token,-2:X2}"); + return result; } + + Debug.Assert(TdsParser.IsValidTdsToken(token), $"DataReady is false, but next token is invalid: {token,-2:X2}"); + } #endif - result = parser.TryRun(RunBehavior.Clean, _command, this, null, stateObj, out _); - if (result != TdsOperationStatus.Done) - { - return result; - } + result = parser.TryRun(RunBehavior.Clean, _command, this, null, stateObj, out _); + if (result != TdsOperationStatus.Done) + { + return result; } } + } - RestoreServerSettings(parser, stateObj); - return TdsOperationStatus.Done; + RestoreServerSettings(parser, stateObj); + return TdsOperationStatus.Done; + } + catch (System.OutOfMemoryException e) + { + _isClosed = true; + aborting = true; + if (_connection != null) + { + _connection.Abort(e); } -#if NETFRAMEWORK && DEBUG - finally + throw; + } + catch (System.StackOverflowException e) + { + _isClosed = true; + aborting = true; + if (_connection != null) { - tdsReliabilitySection.Stop(); + _connection.Abort(e); } -#endif + throw; + } + catch (System.Threading.ThreadAbortException e) + { + _isClosed = true; + aborting = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; } finally { @@ -1117,16 +1162,8 @@ private TdsOperationStatus TryCloseInternal(bool closeReader) #if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); #endif -#if NETFRAMEWORK && DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); try { - tdsReliabilitySection.Start(); -#else - { -#endif // IsClosed may be true if CloseReaderFromConnection was called - in which case, the session has already been closed if (!wasClosed && stateObj != null) { @@ -1146,12 +1183,31 @@ private TdsOperationStatus TryCloseInternal(bool closeReader) } // DO NOT USE stateObj after this point - it has been returned to the TdsParser's session pool and potentially handed out to another thread } -#if NETFRAMEWORK && DEBUG - finally + catch (System.OutOfMemoryException e) { - tdsReliabilitySection.Stop(); + if (_connection != null) + { + _connection.Abort(e); + } + throw; } -#endif + catch (System.StackOverflowException e) + { + if (_connection != null) + { + _connection.Abort(e); + } + throw; + } + catch (System.Threading.ThreadAbortException e) + { + if (_connection != null) + { + _connection.Abort(e); + } + throw; + } + // DO NOT USE stateObj after this point - it has been returned to the TdsParser's session pool and potentially handed out to another thread // do not retry here result = TrySetMetaData(null, false); @@ -1250,7 +1306,7 @@ private TdsOperationStatus TryConsumeMetaData() return TdsOperationStatus.Done; } - /// + /// override public string GetDataTypeName(int i) { SqlStatistics statistics = null; @@ -1326,13 +1382,13 @@ virtual internal SqlBuffer.StorageType GetVariantInternalStorageType(int i) return _data[i].VariantInternalStorageType; } - /// + /// override public IEnumerator GetEnumerator() { return new DbEnumerator(this, IsCommandBehavior(CommandBehavior.CloseConnection)); } - /// + /// #if !NETFRAMEWORK [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] #endif @@ -1439,15 +1495,19 @@ virtual internal int GetLocaleId(int i) return lcid; } - /// + /// override public string GetName(int i) { CheckMetaDataIsReady(columnIndex: i); + + Debug.Assert(_metaData[i].column != null, "MDAC 66681"); return _metaData[i].column; } - /// + /// +#if NET [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] +#endif override public Type GetProviderSpecificFieldType(int i) { SqlStatistics statistics = null; @@ -1519,7 +1579,7 @@ private Type GetProviderSpecificFieldTypeInternal(_SqlMetaData metaData) return providerSpecificFieldType; } - /// + /// // named field access override public int GetOrdinal(string name) { @@ -1540,19 +1600,19 @@ override public int GetOrdinal(string name) } } - /// + /// override public object GetProviderSpecificValue(int i) { return GetSqlValue(i); } - /// + /// override public int GetProviderSpecificValues(object[] values) { return GetSqlValues(values); } - /// + /// public override DataTable GetSchemaTable() { SqlStatistics statistics = null; @@ -1578,14 +1638,14 @@ public override DataTable GetSchemaTable() } } - /// + /// override public bool GetBoolean(int i) { ReadColumn(i); return _data[i].Boolean; } - /// + /// virtual public XmlReader GetXmlReader(int i) { // NOTE: sql_variant can not contain a XML data type: http://msdn.microsoft.com/en-us/library/ms173829.aspx @@ -1625,7 +1685,7 @@ virtual public XmlReader GetXmlReader(int i) } } - /// + /// override public Stream GetStream(int i) { CheckDataIsReady(columnIndex: i); @@ -1673,14 +1733,14 @@ override public Stream GetStream(int i) } } - /// + /// override public byte GetByte(int i) { ReadColumn(i); return _data[i].Byte; } - /// + /// override public long GetBytes(int i, long dataIndex, byte[] buffer, int bufferIndex, int length) { SqlStatistics statistics = null; @@ -1731,19 +1791,12 @@ private TdsOperationStatus TryGetBytesInternal(int i, long dataIndex, byte[] buf { remaining = 0; TdsOperationStatus result; + #if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); #endif -#if NETFRAMEWORK && DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); try { - tdsReliabilitySection.Start(); -#else - { -#endif int cbytes = 0; AssertReaderState(requireData: true, permitAsync: true, columnIndex: i, enforceSequentialAccess: true); @@ -1970,12 +2023,33 @@ private TdsOperationStatus TryGetBytesInternal(int i, long dataIndex, byte[] buf remaining = cbytes; return TdsOperationStatus.Done; } -#if NETFRAMEWORK && DEBUG - finally + catch (System.OutOfMemoryException e) + { + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; + } + catch (System.StackOverflowException e) + { + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; + } + catch (System.Threading.ThreadAbortException e) { - tdsReliabilitySection.Stop(); + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; } -#endif //DEBUG } internal int GetBytesInternalSequential(int i, byte[] buffer, int index, int length, long? timeoutMilliseconds = null) @@ -2027,19 +2101,12 @@ internal TdsOperationStatus TryGetBytesInternalSequential(int i, byte[] buffer, bytesRead = 0; TdsOperationStatus result; + #if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); #endif -#if NETFRAMEWORK && DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); try { - tdsReliabilitySection.Start(); -#else - { -#endif if ((_sharedState._columnDataBytesRemaining == 0) || (length == 0)) { // No data left or nothing requested, return 0 @@ -2081,15 +2148,36 @@ internal TdsOperationStatus TryGetBytesInternalSequential(int i, byte[] buffer, } } } -#if NETFRAMEWORK && DEBUG - finally + catch (System.OutOfMemoryException e) + { + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; + } + catch (System.StackOverflowException e) { - tdsReliabilitySection.Stop(); + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; + } + catch (System.Threading.ThreadAbortException e) + { + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; } -#endif } - /// + /// override public TextReader GetTextReader(int i) { CheckDataIsReady(columnIndex: i); @@ -2162,14 +2250,14 @@ override public TextReader GetTextReader(int i) } } - /// + /// [EditorBrowsableAttribute(EditorBrowsableState.Never)] override public char GetChar(int i) { throw ADP.NotSupported(); } - /// + /// override public long GetChars(int i, long dataIndex, char[] buffer, int bufferIndex, int length) { SqlStatistics statistics = null; @@ -2361,16 +2449,8 @@ private long GetCharsFromPlpData(int i, long dataIndex, char[] buffer, int buffe #if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); #endif -#if NETFRAMEWORK && DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); try { - tdsReliabilitySection.Start(); -#else - { -#endif long cch; AssertReaderState(requireData: true, permitAsync: false, columnIndex: i, enforceSequentialAccess: true); @@ -2459,12 +2539,33 @@ private long GetCharsFromPlpData(int i, long dataIndex, char[] buffer, int buffe _sharedState._columnDataBytesRemaining = (long)_parser.PlpBytesLeft(_stateObj); return cch; } -#if NETFRAMEWORK && DEBUG - finally + catch (System.OutOfMemoryException e) { - tdsReliabilitySection.Stop(); + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; + } + catch (System.StackOverflowException e) + { + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; + } + catch (System.Threading.ThreadAbortException e) + { + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; } -#endif } internal long GetStreamingXmlChars(int i, long dataIndex, char[] buffer, int bufferIndex, int length) @@ -2493,7 +2594,16 @@ internal long GetStreamingXmlChars(int i, long dataIndex, char[] buffer, int buf return cnt; } - /// +#if NETFRAMEWORK + /// + [EditorBrowsableAttribute(EditorBrowsableState.Never)] // MDAC 69508 + IDataReader IDataRecord.GetData(int i) + { + throw ADP.NotSupported(); + } +#endif + + /// override public DateTime GetDateTime(int i) { ReadColumn(i); @@ -2515,77 +2625,77 @@ override public DateTime GetDateTime(int i) return dt; } - /// + /// override public decimal GetDecimal(int i) { ReadColumn(i); return _data[i].Decimal; } - /// + /// override public double GetDouble(int i) { ReadColumn(i); return _data[i].Double; } - /// + /// override public float GetFloat(int i) { ReadColumn(i); return _data[i].Single; } - /// + /// override public Guid GetGuid(int i) { ReadColumn(i); return _data[i].Guid; } - /// + /// override public short GetInt16(int i) { ReadColumn(i); return _data[i].Int16; } - /// + /// override public int GetInt32(int i) { ReadColumn(i); return _data[i].Int32; } - /// + /// override public long GetInt64(int i) { ReadColumn(i); return _data[i].Int64; } - /// + /// virtual public SqlBoolean GetSqlBoolean(int i) { ReadColumn(i); return _data[i].SqlBoolean; } - /// + /// virtual public SqlBinary GetSqlBinary(int i) { ReadColumn(i, setTimeout: true, allowPartiallyReadColumn: true); return _data[i].SqlBinary; } - /// + /// virtual public SqlByte GetSqlByte(int i) { ReadColumn(i); return _data[i].SqlByte; } - /// + /// virtual public SqlBytes GetSqlBytes(int i) { ReadColumn(i); @@ -2593,7 +2703,7 @@ virtual public SqlBytes GetSqlBytes(int i) return new SqlBytes(data); } - /// + /// virtual public SqlChars GetSqlChars(int i) { ReadColumn(i); @@ -2610,70 +2720,70 @@ virtual public SqlChars GetSqlChars(int i) return new SqlChars(data); } - /// + /// virtual public SqlDateTime GetSqlDateTime(int i) { ReadColumn(i); return _data[i].SqlDateTime; } - /// + /// virtual public SqlDecimal GetSqlDecimal(int i) { ReadColumn(i); return _data[i].SqlDecimal; } - /// + /// virtual public SqlGuid GetSqlGuid(int i) { ReadColumn(i); return _data[i].SqlGuid; } - /// + /// virtual public SqlDouble GetSqlDouble(int i) { ReadColumn(i); return _data[i].SqlDouble; } - /// + /// virtual public SqlInt16 GetSqlInt16(int i) { ReadColumn(i); return _data[i].SqlInt16; } - /// + /// virtual public SqlInt32 GetSqlInt32(int i) { ReadColumn(i); return _data[i].SqlInt32; } - /// + /// virtual public SqlInt64 GetSqlInt64(int i) { ReadColumn(i); return _data[i].SqlInt64; } - /// + /// virtual public SqlMoney GetSqlMoney(int i) { ReadColumn(i); return _data[i].SqlMoney; } - /// + /// virtual public SqlSingle GetSqlSingle(int i) { ReadColumn(i); return _data[i].SqlSingle; } - /// + /// virtual public SqlString GetSqlString(int i) { ReadColumn(i); @@ -2686,7 +2796,7 @@ virtual public SqlString GetSqlString(int i) return _data[i].SqlString; } - /// + /// virtual public SqlXml GetSqlXml(int i) { ReadColumn(i); @@ -2717,7 +2827,7 @@ virtual public SqlXml GetSqlXml(int i) return sx; } - /// + /// virtual public SqlJson GetSqlJson(int i) { ReadColumn(i); @@ -2725,7 +2835,19 @@ virtual public SqlJson GetSqlJson(int i) return json; } - /// + /// + virtual public SqlVector GetSqlVector(int i) where T : unmanaged + { + if (typeof(T) != typeof(float)) + { + throw SQL.VectorTypeNotSupported(typeof(T).FullName); + } + + ReadColumn(i); + return _data[i].GetSqlVector(); + } + + /// virtual public object GetSqlValue(int i) { SqlStatistics statistics = null; @@ -2811,7 +2933,7 @@ private object GetSqlValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met } } - /// + /// virtual public int GetSqlValues(object[] values) { SqlStatistics statistics = null; @@ -2840,11 +2962,10 @@ virtual public int GetSqlValues(object[] values) } } - /// + /// override public string GetString(int i) { ReadColumn(i); - // Convert 2008 value to string if type system knob is 2005 or earlier if (_typeSystem <= SqlConnectionString.TypeSystem.SQLServer2005 && _metaData[i].Is2008DateTimeType) { @@ -2854,7 +2975,7 @@ override public string GetString(int i) return _data[i].String; } - /// + /// override public T GetFieldValue(int i) { SqlStatistics statistics = null; @@ -2871,7 +2992,7 @@ override public T GetFieldValue(int i) } } - /// + /// override public object GetValue(int i) { SqlStatistics statistics = null; @@ -2888,7 +3009,7 @@ override public object GetValue(int i) } } - /// + /// virtual public TimeSpan GetTimeSpan(int i) { ReadColumn(i); @@ -2910,7 +3031,7 @@ virtual public TimeSpan GetTimeSpan(int i) return t; } - /// + /// virtual public DateTimeOffset GetDateTimeOffset(int i) { ReadColumn(i); @@ -2974,6 +3095,24 @@ private object GetValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData metaDa { // TypeSystem.SQLServer2005 and above + if (metaData.type == SqlDbTypeExtensions.Vector) + { + if (data.IsNull) + { + return DBNull.Value; + } + else + { + switch (metaData.scale) + { + case (byte)MetaType.SqlVectorElementType.Float32: + return data.GetSqlVector(); + default: + throw SQL.VectorTypeNotSupported(metaData.scale.ToString()); + } + } + } + if (metaData.type != SqlDbType.Udt) { return data.Value; @@ -3077,6 +3216,15 @@ private T GetFieldValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met return (T)(object)data.TimeOnly; } #endif + else if (typeof(T) == typeof(SqlVector)) + { + MetaType metaType = metaData.metaType; + if (metaType.SqlDbType != SqlDbTypeExtensions.Vector) + { + throw SQL.VectorNotSupportedOnColumnType(metaData.column); + } + return (T)(object)data.GetSqlVector(); + } else if (typeof(T) == typeof(XmlReader)) { // XmlReader only allowed on XML types @@ -3215,6 +3363,10 @@ private T GetFieldValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met } else { + if (typeof(T) == typeof(string) && metaData.metaType.SqlDbType == SqlDbTypeExtensions.Vector) + { + return (T)(object)data.String; + } // the requested type is likely to be one that isn't supported so try the cast and // unless there is a null value conversion then feedback the cast exception with // type named to the user so they know what went wrong. Supported types are listed @@ -3233,7 +3385,7 @@ private T GetFieldValueFromSqlBufferInternal(SqlBuffer data, _SqlMetaData met } } - /// + /// override public int GetValues(object[] values) { SqlStatistics statistics = null; @@ -3513,9 +3665,22 @@ private bool IsRowToken(byte token) return TdsEnums.SQLROW == token || TdsEnums.SQLNBCROW == token; } - /// + /// override public bool IsDBNull(int i) { +#if NETFRAMEWORK + if ((IsCommandBehavior(CommandBehavior.SequentialAccess)) && ((_sharedState._nextColumnHeaderToRead > i + 1) || (_lastColumnWithDataChunkRead > i))) + { + // Bug 447026 : A breaking change in System.Data .NET 4.5 for calling IsDBNull on commands in SequentialAccess mode + // http://vstfdevdiv:8080/web/wi.aspx?pcguid=22f9acc9-569a-41ff-b6ac-fac1b6370209&id=447026 + // In .NET 4.0 and previous, it was possible to read a previous column using IsDBNull when in sequential mode + // However, since it had already gone past the column, the current IsNull value is simply returned + + // To replicate this behavior we will skip CheckHeaderIsReady\ReadColumnHeader and instead just check that the reader is ready and the column is valid + CheckMetaDataIsReady(columnIndex: i); + } + else +#endif { CheckHeaderIsReady(columnIndex: i); @@ -3527,13 +3692,13 @@ override public bool IsDBNull(int i) return _data[i].IsNull; } - /// + /// protected internal bool IsCommandBehavior(CommandBehavior condition) { return (condition == (condition & _commandBehavior)); } - /// + /// override public bool NextResult() { if (_currentTask != null) @@ -3561,159 +3726,169 @@ private TdsOperationStatus TryNextResult(out bool more) { #if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); -#endif +#endif try { -#if NETFRAMEWORK && DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); + statistics = SqlStatistics.StartTimer(Statistics); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else + SetTimeout(_defaultTimeoutMilliseconds); + + if (IsClosed) { -#endif - statistics = SqlStatistics.StartTimer(Statistics); + throw ADP.DataReaderClosed(nameof(NextResult)); + } + _fieldNameLookup = null; - SetTimeout(_defaultTimeoutMilliseconds); + bool success = false; // WebData 100390 + _hasRows = false; // reset HasRows - if (IsClosed) + // if we are specifically only processing a single result, then read all the results off the wire and detach + if (IsCommandBehavior(CommandBehavior.SingleResult)) + { + result = TryCloseInternal(closeReader: false); + if (result != TdsOperationStatus.Done) { - throw ADP.DataReaderClosed(nameof(NextResult)); + more = false; + return result; } - _fieldNameLookup = null; - bool success = false; // WebData 100390 - _hasRows = false; // reset HasRows + // In the case of not closing the reader, null out the metadata AFTER + // CloseInternal finishes - since CloseInternal may go to the wire + // and use the metadata. + ClearMetaData(); + more = success; + return TdsOperationStatus.Done; + } - // if we are specifically only processing a single result, then read all the results off the wire and detach - if (IsCommandBehavior(CommandBehavior.SingleResult)) + if (_parser != null) + { + // if there are more rows, then skip them, the user wants the next result + bool moreRows = true; + while (moreRows) { - result = TryCloseInternal(closeReader: false); + result = TryReadInternal(false, out moreRows); if (result != TdsOperationStatus.Done) { + // don't reset set the timeout value more = false; return result; } + } + } - // In the case of not closing the reader, null out the metadata AFTER - // CloseInternal finishes - since CloseInternal may go to the wire - // and use the metadata. - ClearMetaData(); - more = success; - return TdsOperationStatus.Done; + // we may be done, so continue only if we have not detached ourselves from the parser + if (_parser != null) + { + bool moreResults; + result = TryHasMoreResults(out moreResults); + if (result != TdsOperationStatus.Done) + { + more = false; + return result; } - - if (_parser != null) + if (moreResults) { - // if there are more rows, then skip them, the user wants the next result - bool moreRows = true; - while (moreRows) + _metaDataConsumed = false; + _browseModeInfoConsumed = false; + + switch (_altRowStatus) { - result = TryReadInternal(false, out moreRows); - if (result != TdsOperationStatus.Done) - { - // don't reset set the timeout value - more = false; - return result; - } + case ALTROWSTATUS.AltRow: + int altRowId; + result = _parser.TryGetAltRowId(_stateObj, out altRowId); + if (result != TdsOperationStatus.Done) + { + more = false; + return result; + } + _SqlMetaDataSet altMetaDataSet = _altMetaDataSetCollection.GetAltMetaData(altRowId); + if (altMetaDataSet != null) + { + _metaData = altMetaDataSet; + } + Debug.Assert((_metaData != null), "Can't match up altrowmetadata"); + break; + case ALTROWSTATUS.Done: + // restore the row-metaData + _metaData = _altMetaDataSetCollection.metaDataSet; + Debug.Assert(_altRowStatus == ALTROWSTATUS.Done, "invalid AltRowStatus"); + _altRowStatus = ALTROWSTATUS.Null; + break; + default: + result = TryConsumeMetaData(); + if (result != TdsOperationStatus.Done) + { + more = false; + return result; + } + if (_metaData == null) + { + more = false; + return TdsOperationStatus.Done; + } + break; } - } - // we may be done, so continue only if we have not detached ourselves from the parser - if (_parser != null) + success = true; + } + else { - bool moreResults; - result = TryHasMoreResults(out moreResults); + // detach the parser from this reader now + result = TryCloseInternal(closeReader: false); if (result != TdsOperationStatus.Done) { more = false; return result; } - if (moreResults) - { - _metaDataConsumed = false; - _browseModeInfoConsumed = false; - switch (_altRowStatus) - { - case ALTROWSTATUS.AltRow: - int altRowId; - result = _parser.TryGetAltRowId(_stateObj, out altRowId); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - _SqlMetaDataSet altMetaDataSet = _altMetaDataSetCollection.GetAltMetaData(altRowId); - if (altMetaDataSet != null) - { - _metaData = altMetaDataSet; - } - Debug.Assert((_metaData != null), "Can't match up altrowmetadata"); - break; - case ALTROWSTATUS.Done: - // restore the row-metaData - _metaData = _altMetaDataSetCollection.metaDataSet; - Debug.Assert(_altRowStatus == ALTROWSTATUS.Done, "invalid AltRowStatus"); - _altRowStatus = ALTROWSTATUS.Null; - break; - default: - result = TryConsumeMetaData(); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - if (_metaData == null) - { - more = false; - return TdsOperationStatus.Done; - } - break; - } - - success = true; - } - else + // In the case of not closing the reader, null out the metadata AFTER + // CloseInternal finishes - since CloseInternal may go to the wire + // and use the metadata. + result = TrySetMetaData(null, false); + if (result != TdsOperationStatus.Done) { - // detach the parser from this reader now - result = TryCloseInternal(closeReader: false); - if (result != TdsOperationStatus.Done) - { - more = false; - return TdsOperationStatus.Done; - } - - // In the case of not closing the reader, null out the metadata AFTER - // CloseInternal finishes - since CloseInternal may go to the wire - // and use the metadata. - result = TrySetMetaData(null, false); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } + more = false; + return result; } } - else - { - // Clear state in case of Read calling CloseInternal() then user calls NextResult() - // and the case where the Read() above will do essentially the same thing. - ClearMetaData(); - } + } + else + { + // Clear state in case of Read calling CloseInternal() then user calls NextResult() + // and the case where the Read() above will do essentially the same thing. + ClearMetaData(); + } - more = success; - return TdsOperationStatus.Done; + more = success; + return TdsOperationStatus.Done; + } + catch (System.OutOfMemoryException e) + { + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); } -#if NETFRAMEWORK && DEBUG - finally + throw; + } + catch (System.StackOverflowException e) + { + _isClosed = true; + if (_connection != null) { - tdsReliabilitySection.Stop(); + _connection.Abort(e); } -#endif + throw; + } + catch (System.Threading.ThreadAbortException e) + { + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; } finally { @@ -3722,9 +3897,9 @@ private TdsOperationStatus TryNextResult(out bool more) } } - /// + /// // user must call Read() to position on the first row - override public bool Read() + public override bool Read() { if (_currentTask != null) { @@ -3755,140 +3930,117 @@ private TdsOperationStatus TryReadInternal(bool setTimeout, out bool more) try { -#if NETFRAMEWORK && DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); + TdsOperationStatus result; + statistics = SqlStatistics.StartTimer(Statistics); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else + if (_parser != null) { -#endif - TdsOperationStatus result; - statistics = SqlStatistics.StartTimer(Statistics); - - if (_parser != null) + if (setTimeout) { - if (setTimeout) - { - SetTimeout(_defaultTimeoutMilliseconds); - } - if (_sharedState._dataReady) + SetTimeout(_defaultTimeoutMilliseconds); + } + if (_sharedState._dataReady) + { + result = TryCleanPartialRead(); + if (result != TdsOperationStatus.Done) { - result = TryCleanPartialRead(); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } + more = false; + return result; } + } - // clear out our buffers - SqlBuffer.Clear(_data); + // clear out our buffers + SqlBuffer.Clear(_data); - _sharedState._nextColumnHeaderToRead = 0; - _sharedState._nextColumnDataToRead = 0; - _sharedState._columnDataBytesRemaining = -1; // unknown - _lastColumnWithDataChunkRead = -1; + _sharedState._nextColumnHeaderToRead = 0; + _sharedState._nextColumnDataToRead = 0; + _sharedState._columnDataBytesRemaining = -1; // unknown + _lastColumnWithDataChunkRead = -1; - if (!_haltRead) + if (!_haltRead) + { + bool moreRows; + result = TryHasMoreRows(out moreRows); + if (result != TdsOperationStatus.Done) { - bool moreRows; - result = TryHasMoreRows(out moreRows); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - if (moreRows) + more = false; + return result; + } + if (moreRows) + { + // read the row from the backend (unless it's an altrow were the marker is already inside the altrow ...) + while (_stateObj.HasPendingData) { - // read the row from the backend (unless it's an altrow were the marker is already inside the altrow ...) - while (_stateObj.HasPendingData) + if (_altRowStatus != ALTROWSTATUS.AltRow) { - if (_altRowStatus != ALTROWSTATUS.AltRow) + // if this is an ordinary row we let the run method consume the ROW token + result = _parser.TryRun(RunBehavior.ReturnImmediately, _command, this, null, _stateObj, out _sharedState._dataReady); + if (result != TdsOperationStatus.Done) { - // if this is an ordinary row we let the run method consume the ROW token - result = _parser.TryRun(RunBehavior.ReturnImmediately, _command, this, null, _stateObj, out _sharedState._dataReady); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - if (_sharedState._dataReady) - { - break; - } + more = false; + return result; } - else + if (_sharedState._dataReady) { - // ALTROW token and AltrowId are already consumed ... - Debug.Assert(_altRowStatus == ALTROWSTATUS.AltRow, "invalid AltRowStatus"); - _altRowStatus = ALTROWSTATUS.Done; - _sharedState._dataReady = true; break; } } - if (_sharedState._dataReady) + else { - _haltRead = IsCommandBehavior(CommandBehavior.SingleRow); - more = true; - return TdsOperationStatus.Done; + // ALTROW token and AltrowId are already consumed ... + Debug.Assert(_altRowStatus == ALTROWSTATUS.AltRow, "invalid AltRowStatus"); + _altRowStatus = ALTROWSTATUS.Done; + _sharedState._dataReady = true; + break; } } - - if (!_stateObj.HasPendingData) + if (_sharedState._dataReady) { - result = TryCloseInternal(closeReader: false); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } + _haltRead = IsCommandBehavior(CommandBehavior.SingleRow); + more = true; + return TdsOperationStatus.Done; } } - else + + if (!_stateObj.HasPendingData) { - // if we did not get a row and halt is true, clean off rows of result - // success must be false - or else we could have just read off row and set - // halt to true - bool moreRows; - result = TryHasMoreRows(out moreRows); + result = TryCloseInternal(closeReader: false); if (result != TdsOperationStatus.Done) { more = false; return result; } - while (moreRows) + } + } + else + { + // if we did not get a row and halt is true, clean off rows of result + // success must be false - or else we could have just read off row and set + // halt to true + bool moreRows; + result = TryHasMoreRows(out moreRows); + if (result != TdsOperationStatus.Done) + { + more = false; + return result; + } + while (moreRows) + { + // if we are in SingleRow mode, and we've read the first row, + // read the rest of the rows, if any + while (_stateObj.HasPendingData && !_sharedState._dataReady) { - // if we are in SingleRow mode, and we've read the first row, - // read the rest of the rows, if any - while (_stateObj.HasPendingData && !_sharedState._dataReady) - { - result = _parser.TryRun(RunBehavior.ReturnImmediately, _command, this, null, _stateObj, out _sharedState._dataReady); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } - } - - if (_sharedState._dataReady) + result = _parser.TryRun(RunBehavior.ReturnImmediately, _command, this, null, _stateObj, out _sharedState._dataReady); + if (result != TdsOperationStatus.Done) { - result = TryCleanPartialRead(); - if (result != TdsOperationStatus.Done) - { - more = false; - return result; - } + more = false; + return result; } + } - // clear out our buffers - SqlBuffer.Clear(_data); - - _sharedState._nextColumnHeaderToRead = 0; - - result = TryHasMoreRows(out moreRows); + if (_sharedState._dataReady) + { + result = TryCleanPartialRead(); if (result != TdsOperationStatus.Done) { more = false; @@ -3896,38 +4048,44 @@ private TdsOperationStatus TryReadInternal(bool setTimeout, out bool more) } } - // reset haltRead - _haltRead = false; + // clear out our buffers + SqlBuffer.Clear(_data); + + _sharedState._nextColumnHeaderToRead = 0; + + result = TryHasMoreRows(out moreRows); + if (result != TdsOperationStatus.Done) + { + more = false; + return result; + } } + + // reset haltRead + _haltRead = false; } - else if (IsClosed) - { - throw ADP.DataReaderClosed(nameof(Read)); - } - more = false; + } + else if (IsClosed) + { + throw ADP.DataReaderClosed(nameof(Read)); + } + more = false; #if DEBUG - if ((!_sharedState._dataReady) && (_stateObj.HasPendingData)) + if ((!_sharedState._dataReady) && (_stateObj.HasPendingData)) + { + byte token; + result = _stateObj.TryPeekByte(out token); + if (result != TdsOperationStatus.Done) { - byte token; - result = _stateObj.TryPeekByte(out token); - if (result != TdsOperationStatus.Done) - { - return result; - } - - Debug.Assert(TdsParser.IsValidTdsToken(token), $"DataReady is false, but next token is invalid: {token,-2:X2}"); + return result; } -#endif - return TdsOperationStatus.Done; + Debug.Assert(TdsParser.IsValidTdsToken(token), $"DataReady is false, but next token is invalid: {token,-2:X2}"); } -#if NETFRAMEWORK && DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG +#endif + + return TdsOperationStatus.Done; } catch (OutOfMemoryException e) { @@ -3949,9 +4107,6 @@ private TdsOperationStatus TryReadInternal(bool setTimeout, out bool more) } throw; } - /* Even though ThreadAbortException exists in .NET Core, - * since Abort is not supported, the common language runtime won't ever throw ThreadAbortException. - * just to keep a common codebase!*/ catch (System.Threading.ThreadAbortException e) { _isClosed = true; @@ -3987,41 +4142,55 @@ private void ReadColumn(int i, bool setTimeout = true, bool allowPartiallyReadCo private TdsOperationStatus TryReadColumn(int i, bool setTimeout, bool allowPartiallyReadColumn = false, bool forStreaming = false) { CheckDataIsReady(columnIndex: i, permitAsync: true, allowPartiallyReadColumn: allowPartiallyReadColumn, methodName: null); + #if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); #endif -#if NETFRAMEWORK && DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); try { - tdsReliabilitySection.Start(); -#else + Debug.Assert(_sharedState._nextColumnHeaderToRead <= _metaData.Length, "_sharedState._nextColumnHeaderToRead too large"); + Debug.Assert(_sharedState._nextColumnDataToRead <= _metaData.Length, "_sharedState._nextColumnDataToRead too large"); + + if (setTimeout) + { + SetTimeout(_defaultTimeoutMilliseconds); + } + + TdsOperationStatus result = TryReadColumnInternal(i, readHeaderOnly: false, forStreaming: forStreaming); + if (result != TdsOperationStatus.Done) + { + return result; + } + + Debug.Assert(_data[i] != null, " data buffer is null?"); + } + catch (System.OutOfMemoryException e) { -#endif - Debug.Assert(_sharedState._nextColumnHeaderToRead <= _metaData.Length, "_sharedState._nextColumnHeaderToRead too large"); - Debug.Assert(_sharedState._nextColumnDataToRead <= _metaData.Length, "_sharedState._nextColumnDataToRead too large"); - - if (setTimeout) - { - SetTimeout(_defaultTimeoutMilliseconds); - } - - TdsOperationStatus result = TryReadColumnInternal(i, readHeaderOnly: false, forStreaming: forStreaming); - if (result != TdsOperationStatus.Done) - { - return result; - } - - Debug.Assert(_data[i] != null, " data buffer is null?"); - } -#if NETFRAMEWORK && DEBUG - finally + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; + } + catch (System.StackOverflowException e) { - tdsReliabilitySection.Stop(); + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; + } + catch (System.Threading.ThreadAbortException e) + { + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; } -#endif return TdsOperationStatus.Done; } @@ -4068,24 +4237,37 @@ private TdsOperationStatus TryReadColumnHeader(int i) #if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); #endif -#if NETFRAMEWORK && DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); try { - tdsReliabilitySection.Start(); -#else - { -#endif return TryReadColumnInternal(i, readHeaderOnly: true); } -#if NETFRAMEWORK && DEBUG - finally + catch (System.OutOfMemoryException e) { - tdsReliabilitySection.Stop(); + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; + } + catch (System.StackOverflowException e) + { + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; + } + catch (System.Threading.ThreadAbortException e) + { + _isClosed = true; + if (_connection != null) + { + _connection.Abort(e); + } + throw; } -#endif } internal TdsOperationStatus TryReadColumnInternal(int i, bool readHeaderOnly = false, bool forStreaming = false) @@ -4107,8 +4289,8 @@ internal TdsOperationStatus TryReadColumnInternal(int i, bool readHeaderOnly = f Debug.Assert(i == _sharedState._nextColumnDataToRead || // Either we haven't read the column yet ((i + 1 < _sharedState._nextColumnDataToRead) && (IsCommandBehavior(CommandBehavior.SequentialAccess))) || // Or we're in sequential mode and we've read way past the column (i.e. it was not the last column we read) (!_data[i].IsEmpty || _data[i].IsNull) || // Or we should have data stored for the column (unless the column was null) - (_metaData[i].type == SqlDbType.Timestamp), // Or SqlClient: IsDBNull always returns false for timestamp datatype - + (_metaData[i].type == SqlDbType.Timestamp), // Or Dev11 Bug #336820, Dev10 Bug #479607 (SqlClient: IsDBNull always returns false for timestamp datatype) + // Due to a bug in TdsParser.GetNullSqlValue, Timestamps' IsNull is not correctly set - so we need to bypass the check "Gone past column, be we have no data stored for it"); return TdsOperationStatus.Done; } @@ -4551,6 +4733,9 @@ internal TdsOperationStatus TrySetMetaData(_SqlMetaDataSet metaData, bool moreIn _tableNames = null; if (_metaData != null) { +#if NETFRAMEWORK + _metaData.schemaTable = null; +#endif _data = SqlBuffer.CreateBufferArray(metaData.Length); } @@ -4564,9 +4749,10 @@ internal TdsOperationStatus TrySetMetaData(_SqlMetaDataSet metaData, bool moreIn _metaDataConsumed = true; if (_parser != null) - { // There is a valid case where parser is null - // Peek, and if row token present, set _hasRows true since there is a - // row in the result + { + // There is a valid case where parser is null + // Peek, and if row token present, set _hasRows true since there is a + // row in the result byte b; TdsOperationStatus result = _stateObj.TryPeekByte(out b); if (result != TdsOperationStatus.Done) @@ -4761,7 +4947,7 @@ private void AssertReaderState(bool requireData, bool permitAsync, int? columnIn } } - /// + /// public override Task NextResultAsync(CancellationToken cancellationToken) { using (TryEventScope.Create("SqlDataReader.NextResultAsync | API | Object Id {0}", ObjectID)) @@ -4809,7 +4995,7 @@ private static Task NextResultAsyncExecute(Task task, object state) HasNextResultAsyncCallContext context = (HasNextResultAsyncCallContext)state; if (task != null) { - SqlClientEventSource.Log.TryTraceEvent("SqlDataReader.NextResultAsyncExecute | attempt retry {0}", ObjectID); + SqlClientEventSource.Log.TryTraceEvent("SqlDataReader.NextResultAsyncExecute | attempt retry {0}", context.Reader.ObjectID); context.Reader.PrepareForAsyncContinuation(); } @@ -5091,7 +5277,7 @@ out bytesRead return null; } - /// + /// public override Task ReadAsync(CancellationToken cancellationToken) { using (TryEventScope.Create("SqlDataReader.ReadAsync | API | Object Id {0}", ObjectID)) @@ -5283,7 +5469,7 @@ private void SetCachedReadAsyncCallContext(ReadAsyncCallContext instance) } } - /// + /// override public Task IsDBNullAsync(int i, CancellationToken cancellationToken) { try @@ -5434,7 +5620,7 @@ private void SetCachedIDBNullAsyncCallContext(IsDBNullAsyncCallContext instance) } } - /// + /// override public Task GetFieldValueAsync(int i, CancellationToken cancellationToken) { try @@ -5556,24 +5742,7 @@ private static Task GetFieldValueAsyncExecute(Task task, object state) { if (reader.IsCommandBehavior(CommandBehavior.SequentialAccess) && reader._sharedState._dataReady) { - bool internalReadSuccess = false; -#if NETFRAMEWORK - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif - internalReadSuccess = reader.TryReadColumnInternal(context._columnIndex, readHeaderOnly: true) == TdsOperationStatus.Done; - } -#if NETFRAMEWORK - finally - { - tdsReliabilitySection.Stop(); - } -#endif + bool internalReadSuccess = reader.TryReadColumnInternal(context._columnIndex, readHeaderOnly: true) == TdsOperationStatus.Done; if (internalReadSuccess) { @@ -5950,7 +6119,6 @@ private void CompleteAsyncCall(Task task, SqlDataReaderBaseAsyncCallContex } } - internal sealed class Snapshot { public bool _dataReady; @@ -6114,7 +6282,7 @@ private void SwitchToAsyncWithoutSnapshot() _stateObj._asyncReadWithoutSnapshot = true; } - /// + /// public ReadOnlyCollection GetColumnSchema() { SqlStatistics statistics = null; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReaderSmi.stub.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReaderSmi.stub.cs new file mode 100644 index 0000000000..005e7eb633 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDataReaderSmi.stub.cs @@ -0,0 +1,17 @@ +// 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. + +// @TODO: This is only a stub class for clearing errors while merging other files. + +using System.Data; + +namespace Microsoft.Data.SqlClient +{ + internal class SqlDataReaderSmi : SqlDataReader + { + internal SqlDataReaderSmi() : base(null, CommandBehavior.Default) + { + } + } +} diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDbColumn.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDbColumn.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDbColumn.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDbColumn.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs index 174a1eb1bb..02c10569bc 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDelegatedTransaction.cs @@ -86,43 +86,26 @@ public void Initialize() #endif try { -#if NETFRAMEWORK && DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else + if (connection.IsEnlistedInTransaction) { -#endif - if (connection.IsEnlistedInTransaction) - { - SqlClientEventSource.Log.TryTraceEvent("SqlDelegatedTransaction.Initialize | RES | CPOOL | {0}, Client Connection Id {1}, was enlisted, now defecting.", ObjectID, usersConnection?.ClientConnectionId); - - // defect first - connection.EnlistNull(); - } + SqlClientEventSource.Log.TryTraceEvent("SqlDelegatedTransaction.Initialize | RES | CPOOL | {0}, Client Connection Id {1}, was enlisted, now defecting.", ObjectID, usersConnection?.ClientConnectionId); - _internalTransaction = new SqlInternalTransaction(connection, TransactionType.Delegated, null); + // defect first + connection.EnlistNull(); + } - connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Begin, null, _isolationLevel, _internalTransaction, true); + _internalTransaction = new SqlInternalTransaction(connection, TransactionType.Delegated, null); - // Handle case where ExecuteTran didn't produce a new transaction, but also didn't throw. - if (connection.CurrentTransaction == null) - { - connection.DoomThisConnection(); - throw ADP.InternalError(ADP.InternalErrorCode.UnknownTransactionFailure); - } + connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Begin, null, _isolationLevel, _internalTransaction, true); - _active = true; - } -#if NETFRAMEWORK && DEBUG - finally + // Handle case where ExecuteTran didn't produce a new transaction, but also didn't throw. + if (connection.CurrentTransaction == null) { - tdsReliabilitySection.Stop(); + connection.DoomThisConnection(); + throw ADP.InternalError(ADP.InternalErrorCode.UnknownTransactionFailure); } -#endif + + _active = true; } catch (System.OutOfMemoryException e) { @@ -168,69 +151,52 @@ public byte[] Promote() #endif try { -#if NETFRAMEWORK && DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else + lock (connection) { -#endif - lock (connection) + try { - try - { - // Now that we've acquired the lock, make sure we still have valid state for this operation. - ValidateActiveOnConnection(connection); + // Now that we've acquired the lock, make sure we still have valid state for this operation. + ValidateActiveOnConnection(connection); - connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Promote, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true); - returnValue = connection.PromotedDTCToken; + connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Promote, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true); + returnValue = connection.PromotedDTCToken; - // For Global Transactions, we need to set the Transaction Id since we use a Non-MSDTC Promoter type. - if (connection.IsGlobalTransaction) + // For Global Transactions, we need to set the Transaction Id since we use a Non-MSDTC Promoter type. + if (connection.IsGlobalTransaction) + { + if (SysTxForGlobalTransactions.SetDistributedTransactionIdentifier == null) { - if (SysTxForGlobalTransactions.SetDistributedTransactionIdentifier == null) - { - throw SQL.UnsupportedSysTxForGlobalTransactions(); - } - - if (!connection.IsGlobalTransactionsEnabledForServer) - { - throw SQL.GlobalTransactionsNotEnabled(); - } + throw SQL.UnsupportedSysTxForGlobalTransactions(); + } - SysTxForGlobalTransactions.SetDistributedTransactionIdentifier.Invoke(_atomicTransaction, new object[] { this, GetGlobalTxnIdentifierFromToken() }); + if (!connection.IsGlobalTransactionsEnabledForServer) + { + throw SQL.GlobalTransactionsNotEnabled(); } - promoteException = null; + SysTxForGlobalTransactions.SetDistributedTransactionIdentifier.Invoke(_atomicTransaction, new object[] { this, GetGlobalTxnIdentifierFromToken() }); } - catch (SqlException e) - { - promoteException = e; - ADP.TraceExceptionWithoutRethrow(e); + promoteException = null; + } + catch (SqlException e) + { + promoteException = e; - // Doom the connection, to make sure that the transaction is - // eventually rolled back. - // VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event - connection.DoomThisConnection(); - } - catch (InvalidOperationException e) - { - promoteException = e; - ADP.TraceExceptionWithoutRethrow(e); - connection.DoomThisConnection(); - } + ADP.TraceExceptionWithoutRethrow(e); + + // Doom the connection, to make sure that the transaction is + // eventually rolled back. + // VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event + connection.DoomThisConnection(); + } + catch (InvalidOperationException e) + { + promoteException = e; + ADP.TraceExceptionWithoutRethrow(e); + connection.DoomThisConnection(); } } -#if NETFRAMEWORK && DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif } catch (System.OutOfMemoryException e) { @@ -288,10 +254,6 @@ public void Rollback(SinglePhaseEnlistment enlistment) if (connection != null) { -#if NETFRAMEWORK && DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - tdsReliabilitySection.Start(); -#endif //DEBUG SqlConnection usersConnection = connection.Connection; SqlClientEventSource.Log.TryTraceEvent("SqlDelegatedTransaction.Rollback | RES | CPOOL | Object Id {0}, Client Connection Id {1}, rolling back transaction.", ObjectID, usersConnection?.ClientConnectionId); #if NETFRAMEWORK @@ -362,12 +324,6 @@ public void Rollback(SinglePhaseEnlistment enlistment) usersConnection.Abort(e); throw; } -#if NETFRAMEWORK && DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif } else { @@ -393,103 +349,88 @@ public void SinglePhaseCommit(SinglePhaseEnlistment enlistment) try { -#if NETFRAMEWORK && DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - try - { - tdsReliabilitySection.Start(); -#else + // If the connection is doomed, we can be certain that the + // transaction will eventually be rolled back or has already been aborted externally, and we shouldn't + // attempt to commit it. + if (connection.IsConnectionDoomed) { -#endif - // If the connection is doomed, we can be certain that the - // transaction will eventually be rolled back or has already been aborted externally, and we shouldn't - // attempt to commit it. - if (connection.IsConnectionDoomed) + lock (connection) { - lock (connection) - { - _active = false; // set to inactive first, doesn't matter how the rest completes, this transaction is done. - _connection = null; - } - - enlistment.Aborted(SQL.ConnectionDoomed()); + _active = false; // set to inactive first, doesn't matter how the rest completes, this transaction is done. + _connection = null; } - else + + enlistment.Aborted(SQL.ConnectionDoomed()); + } + else + { + Exception commitException; + lock (connection) { - Exception commitException; - lock (connection) + try { - try - { - // Now that we've acquired the lock, make sure we still have valid state for this operation. - ValidateActiveOnConnection(connection); + // Now that we've acquired the lock, make sure we still have valid state for this operation. + ValidateActiveOnConnection(connection); - _active = false; // set to inactive first, doesn't matter how the rest completes, this transaction is done. - _connection = null; // Set prior to ExecuteTransaction call in case this initiates a TransactionEnd event + _active = false; // set to inactive first, doesn't matter how the rest completes, this transaction is done. + _connection = null; // Set prior to ExecuteTransaction call in case this initiates a TransactionEnd event - connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Commit, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true); - commitException = null; - } - catch (SqlException e) - { - commitException = e; + connection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Commit, null, System.Data.IsolationLevel.Unspecified, _internalTransaction, true); + commitException = null; + } + catch (SqlException e) + { + commitException = e; - ADP.TraceExceptionWithoutRethrow(e); + ADP.TraceExceptionWithoutRethrow(e); - // Doom the connection, to make sure that the transaction is - // eventually rolled back. - // VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event - connection.DoomThisConnection(); - } - catch (InvalidOperationException e) - { - commitException = e; - ADP.TraceExceptionWithoutRethrow(e); - connection.DoomThisConnection(); - } + // Doom the connection, to make sure that the transaction is + // eventually rolled back. + // VSTS 144562: doom the connection while having the lock on it to prevent race condition with "Transaction Ended" Event + connection.DoomThisConnection(); } - if (commitException != null) + catch (InvalidOperationException e) { - // connection.ExecuteTransaction failed with exception - if (_internalTransaction.IsCommitted) - { - // Even though we got an exception, the transaction - // was committed by the server. - enlistment.Committed(); - } - else if (_internalTransaction.IsAborted) - { - // The transaction was aborted, report that to - // System.Transactions. - enlistment.Aborted(commitException); - } - else - { - // The transaction is still active, we cannot - // know the state of the transaction. - enlistment.InDoubt(commitException); - } - - // We eat the exception. This is called on the SysTx - // thread, not the applications thread. If we don't - // eat the exception an UnhandledException will occur, - // causing the process to FailFast. + commitException = e; + ADP.TraceExceptionWithoutRethrow(e); + connection.DoomThisConnection(); } - - connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction); - if (commitException == null) + } + if (commitException != null) + { + // connection.ExecuteTransaction failed with exception + if (_internalTransaction.IsCommitted) { - // connection.ExecuteTransaction succeeded + // Even though we got an exception, the transaction + // was committed by the server. enlistment.Committed(); } + else if (_internalTransaction.IsAborted) + { + // The transaction was aborted, report that to + // System.Transactions. + enlistment.Aborted(commitException); + } + else + { + // The transaction is still active, we cannot + // know the state of the transaction. + enlistment.InDoubt(commitException); + } + + // We eat the exception. This is called on the SysTx + // thread, not the applications thread. If we don't + // eat the exception an UnhandledException will occur, + // causing the process to FailFast. + } + + connection.CleanupConnectionOnTransactionCompletion(_atomicTransaction); + if (commitException == null) + { + // connection.ExecuteTransaction succeeded + enlistment.Committed(); } } -#if NETFRAMEWORK && DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif } catch (System.OutOfMemoryException e) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs index e8d474b3da..fa787d76f8 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependency.cs @@ -7,19 +7,22 @@ using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Xml; +using Microsoft.Data.Common; +using Microsoft.Data.Sql; +using Microsoft.Data.SqlClient.ConnectionPool; + #if NETFRAMEWORK using System.IO; using System.Runtime.Remoting; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security.Permissions; +using Interop.Windows.Sni; +using Microsoft.Data.SqlClient.LocalDb; #endif -using System.Text; -using System.Threading; -using System.Xml; -using Microsoft.Data.Common; -using Microsoft.Data.ProviderBase; -using Microsoft.Data.Sql; namespace Microsoft.Data.SqlClient { @@ -284,12 +287,6 @@ public SqlDependency(SqlCommand command, string options, int timeout) long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" {0}, options: '{1}', timeout: '{2}'", ObjectID, options, timeout); try { -#if NETFRAMEWORK - if (InOutOfProcHelper.InProc) - { - throw SQL.SqlDepCannotBeCreatedInProc(); - } -#endif if (timeout < 0) { throw SQL.InvalidSqlDependencyTimeout(nameof(timeout)); @@ -462,7 +459,7 @@ public void AddCommandDependency(SqlCommand command) [ResourceConsumption(ResourceScope.Process, ResourceScope.Process)] private static void ObtainProcessDispatcher() { - byte[] nativeStorage = SNINativeMethodWrapper.GetData(); + byte[] nativeStorage = SqlDependencyProcessDispatcherStorage.NativeGetData(); if (nativeStorage == null) { @@ -470,47 +467,40 @@ private static void ObtainProcessDispatcher() #if DEBUG // Possibly expensive, limit to debug. SqlClientEventSource.Log.TryNotificationTraceEvent(" AppDomain.CurrentDomain.FriendlyName: {0}", AppDomain.CurrentDomain.FriendlyName); - #endif // DEBUG - _AppDomain masterDomain = SNINativeMethodWrapper.GetDefaultAppDomain(); - - if (masterDomain != null) + + _AppDomain masterDomain = AppDomain.CurrentDomain; + + ObjectHandle handle = CreateProcessDispatcher(masterDomain); + if (handle != null) { - ObjectHandle handle = CreateProcessDispatcher(masterDomain); + SqlDependencyProcessDispatcher dependency = (SqlDependencyProcessDispatcher)handle.Unwrap(); - if (handle != null) + if (dependency != null) { - SqlDependencyProcessDispatcher dependency = (SqlDependencyProcessDispatcher)handle.Unwrap(); - - if (dependency != null) - { - s_processDispatcher = SqlDependencyProcessDispatcher.SingletonProcessDispatcher; // Set to static instance. + s_processDispatcher = SqlDependencyProcessDispatcher.SingletonProcessDispatcher; // Set to static instance. - // Serialize and set in native. - using (MemoryStream stream = new()) - { - SqlClientObjRef objRef = new(s_processDispatcher); - DataContractSerializer serializer = new(objRef.GetType()); - GetSerializedObject(objRef, serializer, stream); - SNINativeMethodWrapper.SetData(stream.ToArray()); // Native will be forced to synchronize and not overwrite. - } - } - else + // Serialize and set in native. + using (MemoryStream stream = new()) { - SqlClientEventSource.Log.TryNotificationTraceEvent(" ERROR - ObjectHandle.Unwrap returned null!"); - throw ADP.InternalError(ADP.InternalErrorCode.SqlDependencyObtainProcessDispatcherFailureObjectHandle); + SqlClientObjRef objRef = new(s_processDispatcher); + DataContractSerializer serializer = new(objRef.GetType()); + GetSerializedObject(objRef, serializer, stream); + + // Native will be forced to synchronize and not overwrite. + SqlDependencyProcessDispatcherStorage.NativeSetData(stream.ToArray()); } } else { - SqlClientEventSource.Log.TryNotificationTraceEvent(" ERROR - AppDomain.CreateInstance returned null!"); - throw ADP.InternalError(ADP.InternalErrorCode.SqlDependencyProcessDispatcherFailureCreateInstance); + SqlClientEventSource.Log.TryNotificationTraceEvent(" ERROR - ObjectHandle.Unwrap returned null!"); + throw ADP.InternalError(ADP.InternalErrorCode.SqlDependencyObtainProcessDispatcherFailureObjectHandle); } } else { - SqlClientEventSource.Log.TryNotificationTraceEvent(" ERROR - unable to obtain default AppDomain!"); - throw ADP.InternalError(ADP.InternalErrorCode.SqlDependencyProcessDispatcherFailureAppDomain); + SqlClientEventSource.Log.TryNotificationTraceEvent(" ERROR - AppDomain.CreateInstance returned null!"); + throw ADP.InternalError(ADP.InternalErrorCode.SqlDependencyProcessDispatcherFailureCreateInstance); } } else @@ -567,14 +557,6 @@ internal static bool Start(string connectionString, string queue, bool useDefaul long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" AppDomainKey: '{0}', queue: '{1}'", AppDomainKey, queue); try { -#if NETFRAMEWORK - // The following code exists in Stop as well. It exists here to demand permissions as high in the stack - // as possible. - if (InOutOfProcHelper.InProc) - { - throw SQL.SqlDepCannotBeCreatedInProc(); - } -#endif if (string.IsNullOrEmpty(connectionString)) { if (connectionString == null) @@ -600,7 +582,7 @@ internal static bool Start(string connectionString, string queue, bool useDefaul connectionStringObject.DemandPermission(); if (connectionStringObject.LocalDBInstance != null) { - LocalDBAPI.DemandLocalDBPermissions(); + LocalDbApi.DemandLocalDbPermissions(); } #endif // End duplicate Start/Stop logic. @@ -720,14 +702,6 @@ internal static bool Stop(string connectionString, string queue, bool useDefault long scopeID = SqlClientEventSource.Log.TryNotificationScopeEnterEvent(" AppDomainKey: '{0}', queue: '{1}'", AppDomainKey, queue); try { -#if NETFRAMEWORK - // The following code exists in Stop as well. It exists here to demand permissions as high in the stack - // as possible. - if (InOutOfProcHelper.InProc) - { - throw SQL.SqlDepCannotBeCreatedInProc(); - } -#endif if (string.IsNullOrEmpty(connectionString)) { if (connectionString == null) @@ -753,7 +727,7 @@ internal static bool Stop(string connectionString, string queue, bool useDefault connectionStringObject.DemandPermission(); if (connectionStringObject.LocalDBInstance != null) { - LocalDBAPI.DemandLocalDBPermissions(); + LocalDbApi.DemandLocalDbPermissions(); } #endif // End duplicate Start/Stop logic. diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs index 9e3644a94c..89d54e3196 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlDependencyListener.cs @@ -8,17 +8,18 @@ using System.Data.SqlTypes; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Xml; +using Microsoft.Data.Common; +using Microsoft.Data.SqlClient; +using Microsoft.Data.SqlClient.ConnectionPool; + #if NETFRAMEWORK using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Principal; -using Microsoft.Data; +using Microsoft.Data.SqlClient.LocalDb; #endif -using System.Threading; -using System.Xml; -using Microsoft.Data.Common; -using Microsoft.Data.ProviderBase; -using Microsoft.Data.SqlClient; // This class is the process wide dependency dispatcher. It contains all connection listeners for the entire process and // receives notifications on those connections to dispatch to the corresponding AppDomain dispatcher to notify the @@ -100,7 +101,7 @@ internal SqlConnectionContainer(SqlConnectionContainerHashHelper hashHelper, str if (connStringObj.LocalDBInstance != null) { // If it is LocalDB, we demanded LocalDB permissions too - LocalDBAPI.AssertLocalDBPermissions(); + LocalDbApi.AssertLocalDbPermissions(); } #endif _con.Open(); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs index c48a72dcee..27b3eebcd0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlEnums.cs @@ -63,6 +63,15 @@ internal sealed class MetaType internal readonly bool Is90Supported; internal readonly bool Is100Supported; + // SqlVector Element Types + // + // These underlying values must match the vector "dimension type" values + // in the TDS protocol. + internal enum SqlVectorElementType : byte + { + Float32 = 0x00 + } + public MetaType(byte precision, byte scale, int fixedLength, bool isFixed, bool isLong, bool isPlp, byte tdsType, byte nullableTdsType, string typeName, #if NET [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] @@ -139,6 +148,7 @@ private static bool _IsBinType(SqlDbType type) => type == SqlDbType.VarBinary || type == SqlDbType.Timestamp || type == SqlDbType.Udt || + type == SqlDbTypeExtensions.Vector || (int)type == 24 /*SqlSmallVarBinary*/; private static bool _Is70Supported(SqlDbType type) => @@ -230,6 +240,8 @@ internal static MetaType GetMetaTypeFromSqlDbType(SqlDbType target, bool isMulti return MetaUdt; case SqlDbTypeExtensions.Json: return s_MetaJson; + case SqlDbTypeExtensions.Vector: + return s_MetaVector; case SqlDbType.Structured: if (isMultiValued) { @@ -368,6 +380,8 @@ private static MetaType GetMetaTypeFromValue(Type dataType, object value, bool i return MetaXml; else if (dataType == typeof(SqlJson)) return s_MetaJson; + else if (dataType == typeof(SqlVector)) + return s_MetaVector; else if (dataType == typeof(SqlString)) { return ((inferLen && !((SqlString)value).IsNull) @@ -870,6 +884,8 @@ internal static MetaType GetSqlDataType(int tdsType, uint userType, int length) return MetaDateTimeOffset; case TdsEnums.SQLJSON: return s_MetaJson; + case TdsEnums.SQLVECTOR: + return s_MetaVector; case TdsEnums.SQLVOID: default: @@ -978,6 +994,8 @@ internal static string GetStringFromXml(XmlReader xmlreader) internal static readonly MetaType s_MetaJson = new(255, 255, -1, false, true, true, TdsEnums.SQLJSON, TdsEnums.SQLJSON, MetaTypeName.JSON, typeof(string), typeof(string), SqlDbTypeExtensions.Json, DbType.String, 0); + internal static readonly MetaType s_MetaVector = new(255, 255, -1, false, false, false, TdsEnums.SQLVECTOR, TdsEnums.SQLVECTOR, MetaTypeName.VECTOR, typeof(byte[]), typeof(SqlBinary), SqlDbTypeExtensions.Vector, DbType.Binary, 2); + public static TdsDateTime FromDateTime(DateTime dateTime, byte cb) { SqlDateTime sqlDateTime; @@ -1027,6 +1045,21 @@ internal static int GetTimeSizeFromScale(byte scale) return 5; } + internal static int GetVectorElementSize(byte type) + { + switch (type) + { + case 0: return sizeof(float); + default: + throw SQL.VectorTypeNotSupported(type.ToString()); + } + } + + internal static int GetVectorElementCount(int size, byte elementType) + { + return (size - TdsEnums.VECTOR_HEADER_SIZE) / GetVectorElementSize(elementType); + } + // // please leave string sorted alphabetically // note that these names should only be used in the context of parameters. We always send over BIG* and nullable types for SQL Server @@ -1065,6 +1098,7 @@ private static class MetaTypeName public const string DATETIME2 = "datetime2"; public const string DATETIMEOFFSET = "datetimeoffset"; public const string JSON = "json"; + public const string VECTOR = "vector"; } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlError.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlError.cs index 6390729bad..fc3a1247f3 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlError.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlError.cs @@ -33,16 +33,16 @@ public sealed class SqlError // to find and invoke the functions, changing the signatures will break many // things elsewhere - internal SqlError(int infoNumber, byte errorState, byte errorClass, string server, string errorMessage, string procedure, int lineNumber, uint win32ErrorCode, Exception exception = null) + internal SqlError(int infoNumber, byte errorState, byte errorClass, string server, string errorMessage, string procedure, int lineNumber, int win32ErrorCode, Exception exception = null) : this(infoNumber, errorState, errorClass, server, errorMessage, procedure, lineNumber, win32ErrorCode, exception, -1) { } - internal SqlError(int infoNumber, byte errorState, byte errorClass, string server, string errorMessage, string procedure, int lineNumber, uint win32ErrorCode, Exception exception, int batchIndex) + internal SqlError(int infoNumber, byte errorState, byte errorClass, string server, string errorMessage, string procedure, int lineNumber, int win32ErrorCode, Exception exception, int batchIndex) : this(infoNumber, errorState, errorClass, server, errorMessage, procedure, lineNumber, exception, batchIndex) { _server = server; - _win32ErrorCode = (int)win32ErrorCode; + _win32ErrorCode = win32ErrorCode; } internal SqlError(int infoNumber, byte errorState, byte errorClass, string server, string errorMessage, string procedure, int lineNumber, Exception exception = null) @@ -103,7 +103,7 @@ public override string ToString() /// public int LineNumber => _lineNumber; - internal int Win32ErrorCode => _win32ErrorCode; + internal int Win32ErrorCode => _win32ErrorCode; internal Exception Exception => _exception; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnection.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnection.cs index 8854ce055b..742c8b2865 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnection.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalConnection.cs @@ -27,15 +27,12 @@ internal abstract class SqlInternalConnection : DbConnectionInternal private bool _isGlobalTransactionEnabledForServer; // Whether Global Transactions are enabled for this Azure SQL DB Server private static readonly Guid s_globalTransactionTMID = new("1c742caf-6680-40ea-9c26-6b6846079764"); // ID of the Non-MSDTC, Azure SQL DB Transaction Manager -#if NET internal SqlCommand.ExecuteReaderAsyncCallContext CachedCommandExecuteReaderAsyncContext; internal SqlCommand.ExecuteNonQueryAsyncCallContext CachedCommandExecuteNonQueryAsyncContext; internal SqlCommand.ExecuteXmlReaderAsyncCallContext CachedCommandExecuteXmlReaderAsyncContext; - internal SqlDataReader.Snapshot CachedDataReaderSnapshot; internal SqlDataReader.IsDBNullAsyncCallContext CachedDataReaderIsDBNullContext; internal SqlDataReader.ReadAsyncCallContext CachedDataReaderReadAsyncContext; -#endif // if connection is not open: null // if connection is open: currently active database @@ -198,19 +195,6 @@ internal bool IsGlobalTransactionsEnabledForServer private bool _isAzureSQLConnection = false; // If connected to Azure SQL -#if NETFRAMEWORK - - abstract internal bool Is2000 - { - get; - } - - - abstract internal bool Is2005OrNewer - { - get; - } -#endif internal bool IsAzureSQLConnection { get @@ -231,56 +215,37 @@ override public DbTransaction BeginTransaction(System.Data.IsolationLevel iso) virtual internal SqlTransaction BeginSqlTransaction(System.Data.IsolationLevel iso, string transactionName, bool shouldReconnect) { SqlStatistics statistics = null; -#if NETFRAMEWORK TdsParser bestEffortCleanupTarget = null; +#if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); #endif try { -#if NETFRAMEWORK -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new(); + bestEffortCleanupTarget = GetBestEffortCleanupTarget(Connection); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif // DEBUG - bestEffortCleanupTarget = GetBestEffortCleanupTarget(Connection); -#endif // NETFRAMEWORK - statistics = SqlStatistics.StartTimer(Connection.Statistics); + statistics = SqlStatistics.StartTimer(Connection.Statistics); -#if NETFRAMEWORK - SqlConnection.ExecutePermission.Demand(); // MDAC 81476 -#endif // NETFRAMEWORK - ValidateConnectionForExecute(null); + #if NETFRAMEWORK + SqlConnection.ExecutePermission.Demand(); // MDAC 81476 + #endif - if (HasLocalTransactionFromAPI) - { - throw ADP.ParallelTransactionsNotSupported(Connection); - } + ValidateConnectionForExecute(null); - if (iso == System.Data.IsolationLevel.Unspecified) - { - iso = System.Data.IsolationLevel.ReadCommitted; // Default to ReadCommitted if unspecified. - } - - SqlTransaction transaction = new(this, Connection, iso, AvailableInternalTransaction); - transaction.InternalTransaction.RestoreBrokenConnection = shouldReconnect; - ExecuteTransaction(TransactionRequest.Begin, transactionName, iso, transaction.InternalTransaction, false); - transaction.InternalTransaction.RestoreBrokenConnection = false; - return transaction; -#if NETFRAMEWORK + if (HasLocalTransactionFromAPI) + { + throw ADP.ParallelTransactionsNotSupported(Connection); } -#if DEBUG - finally + + if (iso == System.Data.IsolationLevel.Unspecified) { - tdsReliabilitySection.Stop(); + iso = System.Data.IsolationLevel.ReadCommitted; // Default to ReadCommitted if unspecified. } -#endif // DEBUG -#endif // NETFRAMEWORK + + SqlTransaction transaction = new(this, Connection, iso, AvailableInternalTransaction); + transaction.InternalTransaction.RestoreBrokenConnection = shouldReconnect; + ExecuteTransaction(TransactionRequest.Begin, transactionName, iso, transaction.InternalTransaction, false); + transaction.InternalTransaction.RestoreBrokenConnection = false; + return transaction; } catch (OutOfMemoryException e) { @@ -297,7 +262,7 @@ virtual internal SqlTransaction BeginSqlTransaction(System.Data.IsolationLevel i Connection.Abort(e); #if NETFRAMEWORK BestEffortCleanup(bestEffortCleanupTarget); -#endif // NETFRAMEWORK +#endif throw; } finally @@ -338,44 +303,24 @@ override protected DbReferenceCollection CreateReferenceCollection() override protected void Deactivate() { -#if NETFRAMEWORK TdsParser bestEffortCleanupTarget = null; +#if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); #endif try { SqlClientEventSource.Log.TryAdvancedTraceEvent("SqlInternalConnection.Deactivate | ADV | Object Id {0} deactivating, Client Connection Id {1}", ObjectID, Connection?.ClientConnectionId); -#if NETFRAMEWORK -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif // DEBUG - bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(Connection); -#endif // NETFRAMEWORK - SqlReferenceCollection referenceCollection = (SqlReferenceCollection)ReferenceCollection; - if (referenceCollection != null) - { - referenceCollection.Deactivate(); - } + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(Connection); - // Invoke subclass-specific deactivation logic - InternalDeactivate(); -#if NETFRAMEWORK - } -#if DEBUG - finally + SqlReferenceCollection referenceCollection = (SqlReferenceCollection)ReferenceCollection; + if (referenceCollection != null) { - tdsReliabilitySection.Stop(); + referenceCollection.Deactivate(); } -#endif // DEBUG -#endif // NETFRAMEWORK + + // Invoke subclass-specific deactivation logic + InternalDeactivate(); } catch (OutOfMemoryException) { @@ -679,37 +624,15 @@ override public void EnlistTransaction(Transaction transaction) // enlist in the user specified distributed transaction. This // behavior matches OLEDB and ODBC. -#if NETFRAMEWORK TdsParser bestEffortCleanupTarget = null; +#if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); - try - { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new(); - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#else - { -#endif // DEBUG - bestEffortCleanupTarget = GetBestEffortCleanupTarget(Connection); - Enlist(transaction); - } -#if DEBUG - finally - { - tdsReliabilitySection.Stop(); - } -#endif // DEBUG - } -#else - try +#endif // NETFRAMEWORK + try { + bestEffortCleanupTarget = GetBestEffortCleanupTarget(Connection); Enlist(transaction); } -#endif // NETFRAMEWORK catch (OutOfMemoryException e) { Connection.Abort(e); @@ -785,7 +708,6 @@ internal void OnError(SqlException exception, bool breakConnection, Action> accessTokenCallback = null) + { + } + + internal string InstanceName => null; + + internal bool ThreadHasParserLockForClose { get; set; } + + internal void Dispose() { } + + internal void DoomThisConnection() { } + + internal class SyncAsyncLock + { + internal bool CanBeReleasedFromAnyThread { get; set; } + + internal void Release() { } + + internal void Wait(bool canReleaseFromAnyThread) {} + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs index 127663cd0b..ad89176fe6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlInternalTransaction.cs @@ -52,7 +52,7 @@ internal SqlInternalTransaction(SqlInternalConnection innerConnection, Transacti internal SqlInternalTransaction(SqlInternalConnection innerConnection, TransactionType type, SqlTransaction outerTransaction, long transactionId) { - SqlClientEventSource.Log.TryPoolerTraceEvent("SqlInternalTransaction.ctor | RES | CPOOL | Object Id {0}, Created for connection {1}, outer transaction {2}, Type {3}", ObjectID, innerConnection.ObjectID, outerTransaction?.ObjectID, (int)type); + SqlClientEventSource.Log.TryPoolerTraceEvent("SqlInternalTransaction.ctor | RES | CPOOL | Object Id {0}, Created for connection {1}, outer transaction {2}, Type {3}", ObjectID, innerConnection.ObjectID, outerTransaction?.ObjectId, (int)type); _innerConnection = innerConnection; _transactionType = type; @@ -203,7 +203,6 @@ internal void CloseFromConnection() } finally { - TdsParser.ReliabilitySection.Assert("unreliable call to CloseFromConnection"); // you need to setup for a thread abort somewhere before you call this method if (processFinallyBlock) { // Always ensure we're zombied; 2005 will send an EnvChange that @@ -232,21 +231,7 @@ internal void Commit() // COMMIT ignores transaction names, and so there is no reason to pass it anything. COMMIT // simply commits the transaction from the most recent BEGIN, nested or otherwise. _innerConnection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Commit, null, IsolationLevel.Unspecified, null, false); -#if NETFRAMEWORK - // SQL BU DT 291159 - perform full Zombie on pre-2005, but do not actually - // complete internal transaction until informed by server in the case of 2005 - // or later. - if (!IsZombied && !_innerConnection.Is2005OrNewer) - { - // Since nested transactions are no longer allowed, set flag to false. - // This transaction has been completed. - Zombie(); - } - else -#endif - { - ZombieParent(); - } + ZombieParent(); } catch (Exception e) { @@ -396,15 +381,6 @@ internal void Rollback(string transactionName) try { _innerConnection.ExecuteTransaction(SqlInternalConnection.TransactionRequest.Rollback, transactionName, IsolationLevel.Unspecified, null, false); -#if NETFRAMEWORK - if (!IsZombied && !_innerConnection.Is2005OrNewer) - { - // Check if Zombied before making round-trip to server. - // Against 2005 we receive an envchange on the ExecuteTransaction above on the - // parser that calls back into SqlTransaction for the Zombie() call. - CheckTransactionLevelAndZombie(); - } -#endif } catch (Exception e) { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetadataFactory.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs similarity index 100% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetadataFactory.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs index 1eaf878851..fb9cdbb8e0 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlParameter.cs @@ -14,6 +14,7 @@ using System.IO; using System.Reflection; using System.Text; +using System.Text.Json; using System.Threading; using System.Xml; using Microsoft.Data.Common; @@ -739,6 +740,10 @@ public override object Value { if (ParameterIsSqlType) { + if (_sqlBufferReturnValue.VariantInternalStorageType == SqlBuffer.StorageType.Vector) + { + return GetVectorReturnValue(); + } return _sqlBufferReturnValue.SqlValue; } return _sqlBufferReturnValue.Value; @@ -758,6 +763,30 @@ public override object Value } } + private object GetVectorReturnValue() + { + var elementType = (MetaType.SqlVectorElementType)_sqlBufferReturnValue.GetVectorInfo()._vectorInfo._elementType; + int elementCount = _sqlBufferReturnValue.GetVectorInfo()._vectorInfo._elementCount; + + if (IsNull) + { + switch (elementType) + { + case MetaType.SqlVectorElementType.Float32: + return SqlVector.CreateNull(elementCount); + default: + throw SQL.VectorTypeNotSupported(elementType.ToString()); + } + } + switch (elementType) + { + case MetaType.SqlVectorElementType.Float32: + return new SqlVector((byte[])_sqlBufferReturnValue.Value); + default: + throw SQL.VectorTypeNotSupported(elementType.ToString()); + } + } + /// [ RefreshProperties(RefreshProperties.All), @@ -828,8 +857,13 @@ public override int Size { throw ADP.InvalidSizeValue(value); } - PropertyChanging(); - _size = value; + + // We ignore the Size property for Vector types, as it is not applicable. + if (_metaType == null || _metaType.SqlDbType != SqlDbTypeExtensions.Vector) + { + PropertyChanging(); + _size = value; + } } } } @@ -1603,6 +1637,7 @@ internal int GetActualSize() case SqlDbType.VarBinary: case SqlDbType.Image: case SqlDbType.Timestamp: + case SqlDbTypeExtensions.Vector: coercedSize = (!HasFlag(SqlParameterFlags.IsNull) && (!HasFlag(SqlParameterFlags.CoercedValueIsDataFeed))) ? (BinarySize(val, HasFlag(SqlParameterFlags.CoercedValueIsSqlType))) : 0; _actualSize = (ShouldSerializeSize() ? Size : 0); _actualSize = ((ShouldSerializeSize() && (_actualSize <= coercedSize)) ? _actualSize : coercedSize); @@ -1614,12 +1649,7 @@ internal int GetActualSize() case SqlDbType.Udt: if (!IsNull) { -#if NETFRAMEWORK - //call the static function - coercedSize = AssemblyCache.GetLength(val); -#else coercedSize = SerializationHelperSql9.SizeInBytes(val); -#endif } break; case SqlDbType.Structured: @@ -1901,6 +1931,13 @@ private MetaType GetMetaTypeOnly() { if (_metaType != null) { + if (_metaType.SqlDbType == SqlDbTypeExtensions.Vector && + _direction == ParameterDirection.Input && + (_value == null || _value == DBNull.Value)) + { + _value = DBNull.Value; + return MetaType.GetDefaultMetaType(); + } return _metaType; } if (_value != null && DBNull.Value != _value) @@ -1928,6 +1965,7 @@ private MetaType GetMetaTypeOnly() return MetaType.GetMetaTypeFromType(valueType); } } + return MetaType.GetDefaultMetaType(); } @@ -1937,7 +1975,8 @@ internal void Prepare(SqlCommand cmd) { throw ADP.PrepareParameterType(cmd); } - else if (!ShouldSerializeSize() && !_metaType.IsFixed) + // For vector datatype we do not require size to be specified. It is inferred from the SqlParameter.Value. + else if (!ShouldSerializeSize() && !_metaType.IsFixed && _metaType.SqlDbType != SqlDbTypeExtensions.Vector) { throw ADP.PrepareParameterSize(cmd); } @@ -1993,7 +2032,7 @@ internal void Validate(int index, bool isCommandProc) (SqlDbType != SqlDbType.Udt) && // BUG: (VSTFDevDiv - 479609): Output parameter with size 0 throws for XML, TEXT, NTEXT, IMAGE. // NOTE: (VSTFDevDiv - 479609): Not Fixed for TEXT, NTEXT, IMAGE as these are deprecated LOB types. - (SqlDbType != SqlDbType.Xml) && + (SqlDbType != SqlDbType.Xml && SqlDbType != SqlDbTypeExtensions.Json) && !metaType.IsVarTime ) { @@ -2005,6 +2044,13 @@ internal void Validate(int index, bool isCommandProc) GetCoercedValue(); } + if (metaType.SqlDbType == SqlDbTypeExtensions.Vector && + (_value == null || _value == DBNull.Value) && + (Direction == ParameterDirection.Output || Direction == ParameterDirection.InputOutput)) + { + throw ADP.NullOutputParameterValueForVector(_parameterName); + } + //check if the UdtTypeName is specified for Udt params if (metaType.SqlDbType == SqlDbType.Udt) { @@ -2154,6 +2200,10 @@ private int ValueSize(object value) } return sqlString.Value.Length; } + if (value is ISqlVector sqlVector) + { + return sqlVector.Size; + } if (value is SqlChars sqlChars) { if (sqlChars.IsNull) @@ -2325,6 +2375,21 @@ internal static object CoerceValue(object value, MetaType destinationType, out b value = ((TimeOnly)value).ToTimeSpan(); } #endif + else if (currentType == typeof(SqlVector)) + { + value = ((ISqlVector)value).VectorPayload; + } + else if (currentType == typeof(string) && destinationType.SqlDbType == SqlDbTypeExtensions.Vector) + { + try + { + value = (new SqlVector(JsonSerializer.Deserialize(value as string)) as ISqlVector).VectorPayload; + } + catch (Exception ex) when (ex is ArgumentNullException || ex is JsonException) + { + throw ADP.InvalidJsonStringForVector(value as string, ex); + } + } else if ( TdsEnums.SQLTABLE == destinationType.TDSType && ( diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs index c97e51f577..52b965325d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialStream.cs @@ -4,13 +4,14 @@ using System; using System.Diagnostics; +using System.IO; using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; namespace Microsoft.Data.SqlClient { - sealed internal class SqlSequentialStream : System.IO.Stream + internal sealed class SqlSequentialStream : Stream { private SqlDataReader _reader; // The SqlDataReader that we are reading data from private readonly int _columnIndex; // The index of out column in the table @@ -302,7 +303,7 @@ public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, Asy Task readTask = ReadAsync(buffer, offset, count, CancellationToken.None); if (callback != null) { - readTask.ContinueWith((t) => callback(t), TaskScheduler.Default); + readTask.ContinueWith(t => callback(t), TaskScheduler.Default); } return readTask; } @@ -328,11 +329,19 @@ public override int EndRead(IAsyncResult asyncResult) return readTask.Result; } #else - public override IAsyncResult BeginRead(byte[] array, int offset, int count, AsyncCallback asyncCallback, object asyncState) => - TaskToApm.Begin(ReadAsync(array, offset, count, CancellationToken.None), asyncCallback, asyncState); + public override IAsyncResult BeginRead( + byte[] array, + int offset, + int count, + AsyncCallback asyncCallback, + object asyncState) + { + Task readTask = ReadAsync(array, offset, count, CancellationToken.None); + return TaskToAsyncResult.Begin(readTask, asyncCallback, asyncState); + } public override int EndRead(IAsyncResult asyncResult) => - TaskToApm.End(asyncResult); + TaskToAsyncResult.End(asyncResult); #endif } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs index a563d0dfc6..3ae05e0386 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSequentialTextReader.cs @@ -172,7 +172,7 @@ public override Task ReadAsync(char[] buffer, int index, int count) byte[] byteBuffer = PrepareByteBuffer(charsNeeded, out int byteBufferUsed); // Permit a 0 byte read in order to advance the reader to the correct column - if ((byteBufferUsed < byteBuffer.Length) || (byteBuffer.Length == 0)) + if (byteBufferUsed <= byteBuffer.Length || byteBuffer.Length == 0) { SqlDataReader reader = _reader; if (reader != null) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs index fb9ea2997d..961746fc2d 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlSymmetricKeyCache.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Text; +using System.Threading; using Microsoft.Extensions.Caching.Memory; namespace Microsoft.Data.SqlClient @@ -15,8 +16,8 @@ namespace Microsoft.Data.SqlClient sealed internal class SqlSymmetricKeyCache { private readonly MemoryCache _cache; - private static readonly SqlSymmetricKeyCache _singletonInstance = new SqlSymmetricKeyCache(); - + private static readonly SqlSymmetricKeyCache _singletonInstance = new(); + private static SemaphoreSlim _cacheLock = new(1, 1); private SqlSymmetricKeyCache() { @@ -35,7 +36,7 @@ internal SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnectio { string serverName = connection.DataSource; Debug.Assert(serverName is not null, @"serverName should not be null."); - StringBuilder cacheLookupKeyBuilder = new StringBuilder(serverName, capacity: serverName.Length + SqlSecurityUtility.GetBase64LengthFromByteLength(keyInfo.encryptedKey.Length) + keyInfo.keyStoreName.Length + 2/*separators*/); + StringBuilder cacheLookupKeyBuilder = new(serverName, capacity: serverName.Length + SqlSecurityUtility.GetBase64LengthFromByteLength(keyInfo.encryptedKey.Length) + keyInfo.keyStoreName.Length + 2/*separators*/); #if DEBUG int capacity = cacheLookupKeyBuilder.Capacity; @@ -52,54 +53,66 @@ internal SqlClientSymmetricKey GetKey(SqlEncryptionKeyInfo keyInfo, SqlConnectio Debug.Assert(cacheLookupKey.Length <= capacity, "We needed to allocate a larger array"); #endif //DEBUG - // Lookup the key in cache - SqlClientSymmetricKey encryptionKey; - if (!(_cache.TryGetValue(cacheLookupKey, out encryptionKey))) - { - Debug.Assert(SqlConnection.ColumnEncryptionTrustedMasterKeyPaths is not null, @"SqlConnection.ColumnEncryptionTrustedMasterKeyPaths should not be null"); - - SqlSecurityUtility.ThrowIfKeyPathIsNotTrustedForServer(serverName, keyInfo.keyPath); + // Acquire the lock to ensure thread safety when accessing the cache + _cacheLock.Wait(); - // Key Not found, attempt to look up the provider and decrypt CEK - if (!SqlSecurityUtility.TryGetColumnEncryptionKeyStoreProvider(keyInfo.keyStoreName, out SqlColumnEncryptionKeyStoreProvider provider, connection, command)) + try + { + // Lookup the key in cache + if (!(_cache.TryGetValue(cacheLookupKey, out SqlClientSymmetricKey encryptionKey))) { - throw SQL.UnrecognizedKeyStoreProviderName(keyInfo.keyStoreName, - SqlConnection.GetColumnEncryptionSystemKeyStoreProvidersNames(), - SqlSecurityUtility.GetListOfProviderNamesThatWereSearched(connection, command)); - } + Debug.Assert(SqlConnection.ColumnEncryptionTrustedMasterKeyPaths is not null, @"SqlConnection.ColumnEncryptionTrustedMasterKeyPaths should not be null"); - // Decrypt the CEK - // We will simply bubble up the exception from the DecryptColumnEncryptionKey function. - byte[] plaintextKey; - try - { - // to prevent conflicts between CEK caches, global providers should not use their own CEK caches - provider.ColumnEncryptionKeyCacheTtl = new TimeSpan(0); - plaintextKey = provider.DecryptColumnEncryptionKey(keyInfo.keyPath, keyInfo.algorithmName, keyInfo.encryptedKey); - } - catch (Exception e) - { - // Generate a new exception and throw. - string keyHex = SqlSecurityUtility.GetBytesAsString(keyInfo.encryptedKey, fLast: true, countOfBytes: 10); - throw SQL.KeyDecryptionFailed(keyInfo.keyStoreName, keyHex, e); - } + SqlSecurityUtility.ThrowIfKeyPathIsNotTrustedForServer(serverName, keyInfo.keyPath); - encryptionKey = new SqlClientSymmetricKey(plaintextKey); + // Key Not found, attempt to look up the provider and decrypt CEK + if (!SqlSecurityUtility.TryGetColumnEncryptionKeyStoreProvider(keyInfo.keyStoreName, out SqlColumnEncryptionKeyStoreProvider provider, connection, command)) + { + throw SQL.UnrecognizedKeyStoreProviderName(keyInfo.keyStoreName, + SqlConnection.GetColumnEncryptionSystemKeyStoreProvidersNames(), + SqlSecurityUtility.GetListOfProviderNamesThatWereSearched(connection, command)); + } + + // Decrypt the CEK + // We will simply bubble up the exception from the DecryptColumnEncryptionKey function. + byte[] plaintextKey; + try + { + // AKV provider registration supports multi-user scenarios, so it is not safe to cache the CEK in the global provider. + // The CEK cache is a global cache, and is shared across all connections. + // To prevent conflicts between CEK caches, global providers should not use their own CEK caches + provider.ColumnEncryptionKeyCacheTtl = new TimeSpan(0); + plaintextKey = provider.DecryptColumnEncryptionKey(keyInfo.keyPath, keyInfo.algorithmName, keyInfo.encryptedKey); + } + catch (Exception e) + { + // Generate a new exception and throw. + string keyHex = SqlSecurityUtility.GetBytesAsString(keyInfo.encryptedKey, fLast: true, countOfBytes: 10); + throw SQL.KeyDecryptionFailed(keyInfo.keyStoreName, keyHex, e); + } - // If the cache TTL is zero, don't even bother inserting to the cache. - if (SqlConnection.ColumnEncryptionKeyCacheTtl != TimeSpan.Zero) - { - // In case multiple threads reach here at the same time, the first one wins. - // The allocated memory will be reclaimed by Garbage Collector. - MemoryCacheEntryOptions options = new MemoryCacheEntryOptions + encryptionKey = new SqlClientSymmetricKey(plaintextKey); + + // If the cache TTL is zero, don't even bother inserting to the cache. + if (SqlConnection.ColumnEncryptionKeyCacheTtl != TimeSpan.Zero) { - AbsoluteExpirationRelativeToNow = SqlConnection.ColumnEncryptionKeyCacheTtl - }; - _cache.Set(cacheLookupKey, encryptionKey, options); + // In case multiple threads reach here at the same time, the first one wins. + // The allocated memory will be reclaimed by Garbage Collector. + MemoryCacheEntryOptions options = new() + { + AbsoluteExpirationRelativeToNow = SqlConnection.ColumnEncryptionKeyCacheTtl + }; + _cache.Set(cacheLookupKey, encryptionKey, options); + } } - } - return encryptionKey; + return encryptionKey; + } + finally + { + // Release the lock to allow other threads to access the cache + _cacheLock.Release(); + } } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.Common.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.Common.cs deleted file mode 100644 index e96f854e50..0000000000 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.Common.cs +++ /dev/null @@ -1,145 +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.Data; -using System.Data.Common; -using System.Diagnostics; -using Microsoft.Data.Common; - -namespace Microsoft.Data.SqlClient -{ - /// - public sealed partial class SqlTransaction : DbTransaction - { - private static int s_objectTypeCount; // EventSource Counter - internal readonly int _objectID = System.Threading.Interlocked.Increment(ref s_objectTypeCount); - internal readonly IsolationLevel _isolationLevel = IsolationLevel.ReadCommitted; - - private SqlInternalTransaction _internalTransaction; - private readonly SqlConnection _connection; - - private bool _isFromAPI; - - internal SqlTransaction(SqlInternalConnection internalConnection, SqlConnection con, - IsolationLevel iso, SqlInternalTransaction internalTransaction) - { -#if NETFRAMEWORK - SqlConnection.VerifyExecutePermission(); -#endif - _isolationLevel = iso; - _connection = con; - - if (internalTransaction == null) - { - _internalTransaction = new SqlInternalTransaction(internalConnection, TransactionType.LocalFromAPI, this); - } - else - { - Debug.Assert(internalConnection.CurrentTransaction == internalTransaction, "Unexpected Parser.CurrentTransaction state!"); - _internalTransaction = internalTransaction; - _internalTransaction.InitParent(this); - } - } - - //////////////////////////////////////////////////////////////////////////////////////// - // PROPERTIES - //////////////////////////////////////////////////////////////////////////////////////// - - /// - public new SqlConnection Connection - {// MDAC 66655 - get - { - if (IsZombied) - { - return null; - } - else - { - return _connection; - } - } - } - - /// - protected override DbConnection DbConnection => Connection; - - internal SqlInternalTransaction InternalTransaction => _internalTransaction; - - /// - public override IsolationLevel IsolationLevel - { - get - { - ZombieCheck(); - return _isolationLevel; - } - } - - private bool Is2005PartialZombie => _internalTransaction !=null && _internalTransaction.IsCompleted; - - internal bool IsZombied => _internalTransaction == null || _internalTransaction.IsCompleted; - - internal int ObjectID => _objectID; - - internal SqlStatistics Statistics - { - get - { - if (_connection != null) - { - if (_connection.StatisticsEnabled) - { - return _connection.Statistics; - } - } - return null; - } - } - - //////////////////////////////////////////////////////////////////////////////////////// - // INTERNAL METHODS - //////////////////////////////////////////////////////////////////////////////////////// - - internal void Zombie() - { - // For Yukon, we have to defer "zombification" until - // we get past the users' next rollback, else we'll - // throw an exception there that is a breaking change. - // Of course, if the connection is already closed, - // then we're free to zombify... - SqlInternalConnection internalConnection = (_connection.InnerConnection as SqlInternalConnection); - if (internalConnection != null -#if NETFRAMEWORK - && internalConnection.Is2005OrNewer -#endif - && !_isFromAPI) - { - SqlClientEventSource.Log.TryAdvancedTraceEvent("SqlTransaction.Zombie | ADV | Object Id {0} yukon deferred zombie", ObjectID); - } - else - { - _internalTransaction = null; // pre SQL 2005 zombification - } - } - - //////////////////////////////////////////////////////////////////////////////////////// - // PRIVATE METHODS - //////////////////////////////////////////////////////////////////////////////////////// - - private void ZombieCheck() - { - // If this transaction has been completed, throw exception since it is unusable. - if (IsZombied) - { - if (Is2005PartialZombie) - { - _internalTransaction = null; // SQL 2005 zombification - } - - throw ADP.TransactionZombied(this); - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.cs new file mode 100644 index 0000000000..8130b529d7 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlTransaction.cs @@ -0,0 +1,470 @@ +// 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.ComponentModel; +using System.Data; +using System.Data.Common; +using System.Diagnostics; +using System.Threading; +using Microsoft.Data.Common; + +#if NET +using Microsoft.Data.SqlClient.Diagnostics; +#else +using System.Runtime.CompilerServices; +#endif + +namespace Microsoft.Data.SqlClient +{ + /// + public sealed class SqlTransaction : DbTransaction + { + #if NET + private static readonly SqlDiagnosticListener s_diagnosticListener = new(); + #endif + + private static int s_objectTypeCount; // EventSource Counter + + private readonly SqlConnection _connection; + private readonly IsolationLevel _isolationLevel; + private bool _isFromApi; + + internal SqlTransaction( + SqlInternalConnection internalConnection, + SqlConnection con, + IsolationLevel iso, + SqlInternalTransaction internalTransaction) + { + #if NETFRAMEWORK + SqlConnection.VerifyExecutePermission(); + #endif + _isolationLevel = iso; + _connection = con; + + if (internalTransaction == null) + { + InternalTransaction = new SqlInternalTransaction(internalConnection, TransactionType.LocalFromAPI, this); + } + else + { + Debug.Assert(internalConnection.CurrentTransaction == internalTransaction, "Unexpected Parser.CurrentTransaction state!"); + InternalTransaction = internalTransaction; + InternalTransaction.InitParent(this); + } + } + + #region Properties + + /// + public new SqlConnection Connection => IsZombied ? null : _connection; + + /// + public override IsolationLevel IsolationLevel + { + get + { + ZombieCheck(); + return _isolationLevel; + } + } + + internal SqlInternalTransaction InternalTransaction { get; private set; } + + internal bool IsZombied => InternalTransaction == null || InternalTransaction.IsCompleted; + + internal int ObjectId { get; } = Interlocked.Increment(ref s_objectTypeCount); + + /// + protected override DbConnection DbConnection => Connection; + + private bool Is2005PartialZombie => InternalTransaction?.IsCompleted == true; + + private SqlStatistics Statistics => _connection?.StatisticsEnabled == true ? _connection.Statistics : null; + + #endregion + + /// + public override void Commit() + { + #if NET + using DiagnosticTransactionScope diagnosticScope = s_diagnosticListener.CreateTransactionCommitScope( + _isolationLevel, + _connection, + InternalTransaction); + #endif + + ZombieCheck(); + + using (TryEventScope.Create("SqlTransaction.Commit | API | Object Id {0}", ObjectId)) + { + SqlStatistics statistics = null; + + SqlClientEventSource.Log.TryCorrelationTraceEvent( + "SqlTransaction.Commit | API | Correlation | Object Id {0}, Activity Id {1}, Client Connection Id {2}", + ObjectId, + ActivityCorrelator.Current, + Connection?.ClientConnectionId); + + #if NETFRAMEWORK + TdsParser bestEffortCleanupTarget = null; + RuntimeHelpers.PrepareConstrainedRegions(); + #endif + try + { + #if NETFRAMEWORK + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); + #endif + + statistics = SqlStatistics.StartTimer(Statistics); + + _isFromApi = true; + + InternalTransaction.Commit(); + } + #if NETFRAMEWORK + catch (OutOfMemoryException e) + { + _connection.Abort(e); + throw; + } + catch (StackOverflowException e) + { + _connection.Abort(e); + throw; + } + catch (ThreadAbortException e) + { + _connection.Abort(e); + SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); + throw; + } + #endif + catch (SqlException ex) + { + #if NET + diagnosticScope.SetException(ex); + #endif + + // GitHub Issue #130 - When a timeout exception has occurred on transaction completion request, + // this connection may not be in reusable state. + // We will abort this connection and make sure it does not go back to the pool. + if (ex.InnerException is Win32Exception innerException && innerException.NativeErrorCode == TdsEnums.SNI_WAIT_TIMEOUT) + { + _connection.Abort(ex); + } + throw; + } + #if NET + catch (Exception ex) + { + diagnosticScope.SetException(ex); + throw; + } + #endif + finally + { + SqlStatistics.StopTimer(statistics); + _isFromApi = false; + } + } + } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + { + #if NETFRAMEWORK + TdsParser bestEffortCleanupTarget = null; + RuntimeHelpers.PrepareConstrainedRegions(); + #endif + try + { + #if NETFRAMEWORK + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); + #endif + + if (!IsZombied && !Is2005PartialZombie) + { + InternalTransaction.Dispose(); + } + } + catch (OutOfMemoryException e) + { + _connection.Abort(e); + throw; + } + catch (StackOverflowException e) + { + _connection.Abort(e); + throw; + } + catch (ThreadAbortException e) + { + _connection.Abort(e); + + #if NETFRAMEWORK + SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); + #endif + + throw; + } + } + + base.Dispose(disposing); + } + + /// + public override void Rollback() + { + #if NET + using DiagnosticTransactionScope diagnosticScope = s_diagnosticListener.CreateTransactionRollbackScope( + _isolationLevel, + _connection, + InternalTransaction, + transactionName: null); + #endif + + if (Is2005PartialZombie) + { + // Put something in the trace in case a customer has an issue + SqlClientEventSource.Log.TryAdvancedTraceEvent( + "SqlTransaction.Rollback | ADV | Object Id {0}, partial zombie no rollback required", + ObjectId); + + InternalTransaction = null; // 2005 zombification + } + else + { + ZombieCheck(); + + SqlStatistics statistics = null; + using (TryEventScope.Create("SqlTransaction.Rollback | API | Object Id {0}", ObjectId)) + { + SqlClientEventSource.Log.TryCorrelationTraceEvent( + "SqlTransaction.Rollback | API | Correlation | Object Id {0}, ActivityID {1}, Client Connection Id {2}", + ObjectId, + ActivityCorrelator.Current, + Connection?.ClientConnectionId); + + #if NETFRAMEWORK + TdsParser bestEffortCleanupTarget = null; + RuntimeHelpers.PrepareConstrainedRegions(); + #endif + try + { + #if NETFRAMEWORK + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); + #endif + + statistics = SqlStatistics.StartTimer(Statistics); + + _isFromApi = true; + InternalTransaction.Rollback(); + } + #if NETFRAMEWORK + catch (OutOfMemoryException e) + { + _connection.Abort(e); + throw; + } + catch (StackOverflowException e) + { + _connection.Abort(e); + throw; + } + catch (ThreadAbortException e) + { + _connection.Abort(e); + SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); + throw; + } + #else + catch (Exception ex) + { + diagnosticScope.SetException(ex); + throw; + } + #endif + finally + { + SqlStatistics.StopTimer(statistics); + _isFromApi = false; + } + } + } + } + + /// + #if NET + public override void Rollback(string transactionName) + #else + public void Rollback(string transactionName) + #endif + { + #if NET + using DiagnosticTransactionScope diagnosticScope = s_diagnosticListener.CreateTransactionRollbackScope( + _isolationLevel, + _connection, + InternalTransaction, + transactionName); + #endif + + #if NETFRAMEWORK + SqlConnection.ExecutePermission.Demand(); // MDAC 81476 + #endif + + ZombieCheck(); + + var eventScopeEnter = TryEventScope.Create(SqlClientEventSource.Log.TryScopeEnterEvent( + "SqlTransaction.Rollback | API | Object Id {0}, Transaction Name='{1}', ActivityID {2}, Client Connection Id {3}", + ObjectId, + transactionName, + ActivityCorrelator.Current, + Connection?.ClientConnectionId)); + using (eventScopeEnter) + { + SqlStatistics statistics = null; + + #if NETFRAMEWORK + TdsParser bestEffortCleanupTarget = null; + RuntimeHelpers.PrepareConstrainedRegions(); + #endif + try + { + #if NETFRAMEWORK + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); + #endif + + statistics = SqlStatistics.StartTimer(Statistics); + + _isFromApi = true; + InternalTransaction.Rollback(transactionName); + } + #if NETFRAMEWORK + catch (OutOfMemoryException e) + { + _connection.Abort(e); + throw; + } + catch (StackOverflowException e) + { + _connection.Abort(e); + throw; + } + catch (ThreadAbortException e) + { + _connection.Abort(e); + SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); + throw; + } + #else + catch (Exception ex) + { + diagnosticScope.SetException(ex); + throw; + } + #endif + finally + { + SqlStatistics.StopTimer(statistics); + _isFromApi = false; + } + } + } + + /// + #if NET + public override void Save(string savePointName) + #else + public void Save(string savePointName) + #endif + { + #if NETFRAMEWORK + SqlConnection.ExecutePermission.Demand(); // MDAC 81476 + #endif + + ZombieCheck(); + + SqlStatistics statistics = null; + using (TryEventScope.Create("SqlTransaction.Save | API | Object Id {0} | Save Point Name '{1}'", ObjectId, savePointName)) + { + #if NETFRAMEWORK + TdsParser bestEffortCleanupTarget = null; + RuntimeHelpers.PrepareConstrainedRegions(); + #endif + try + { + #if NETFRAMEWORK + bestEffortCleanupTarget = SqlInternalConnection.GetBestEffortCleanupTarget(_connection); + #endif + + statistics = SqlStatistics.StartTimer(Statistics); + + InternalTransaction.Save(savePointName); + } + #if NETFRAMEWORK + catch (OutOfMemoryException e) + { + _connection.Abort(e); + throw; + } + catch (StackOverflowException e) + { + _connection.Abort(e); + throw; + } + catch (ThreadAbortException e) + { + _connection.Abort(e); + + #if NETFRAMEWORK + SqlInternalConnection.BestEffortCleanup(bestEffortCleanupTarget); + #endif + + throw; + } + #endif + finally + { + SqlStatistics.StopTimer(statistics); + } + } + } + + internal void Zombie() + { + // For Yukon, we have to defer "zombification" until we get past the users' next + // rollback, else we'll throw an exception there that is a breaking change. Of course, + // if the connection is already closed, then we're free to zombify... + if (_connection.InnerConnection is SqlInternalConnection internalConnection && !_isFromApi) + { + SqlClientEventSource.Log.TryAdvancedTraceEvent( + "SqlTransaction.Zombie | ADV | Object Id {0} yukon deferred zombie", + ObjectId); + } + else + { + // pre SQL 2005 zombification + InternalTransaction = null; + } + } + + private void ZombieCheck() + { + // If this transaction has been completed, throw exception since it is unusable. + if (IsZombied) + { + if (Is2005PartialZombie) + { + // SQL 2005 zombification + InternalTransaction = null; + } + + throw ADP.TransactionZombied(this); + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs index 67e082a0bc..06564633e8 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlUtil.cs @@ -183,21 +183,7 @@ internal static void ContinueTask(Task task, RuntimeHelpers.PrepareConstrainedRegions(); try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#endif //DEBUG - onSuccess(); -#if DEBUG - } - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG + onSuccess(); } catch (System.OutOfMemoryException e) { @@ -279,12 +265,12 @@ internal static void ContinueTaskWithState(Task task, Action onFailure = null, Action onCancellation = null, #if NET - Func exceptionConverter = null + Func exceptionConverter = null, #else Func exceptionConverter = null, +#endif SqlInternalConnectionTds connectionToDoom = null, SqlConnection connectionToAbort = null -#endif ) { #if NETFRAMEWORK @@ -324,27 +310,14 @@ internal static void ContinueTaskWithState(Task task, completion.TrySetCanceled(); } } -#if NETFRAMEWORK else if (connectionToDoom != null || connectionToAbort != null) { +#if NETFRAMEWORK RuntimeHelpers.PrepareConstrainedRegions(); +#endif try { -#if DEBUG - TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection(); - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - tdsReliabilitySection.Start(); -#endif //DEBUG - onSuccess(state2); -#if DEBUG - } - finally - { - tdsReliabilitySection.Stop(); - } -#endif //DEBUG + onSuccess(state2); } catch (System.OutOfMemoryException e) { @@ -390,7 +363,6 @@ internal static void ContinueTaskWithState(Task task, completion.SetException(e); } } -#endif else { try @@ -444,26 +416,30 @@ internal static void SetTimeoutException(TaskCompletionSource completion ); } } - -#if NET - internal static void SetTimeoutExceptionWithState(TaskCompletionSource completion, int timeout, object state, Func onFailure, CancellationToken cancellationToken) + + internal static void SetTimeoutExceptionWithState( + TaskCompletionSource completion, + int timeout, + object state, + Func onFailure, + CancellationToken cancellationToken) { - if (timeout > 0) + if (timeout <= 0) { - Task.Delay(timeout * 1000, cancellationToken).ContinueWith( - (Task task, object state) => - { - if (!task.IsCanceled && !completion.Task.IsCompleted) - { - completion.TrySetException(onFailure(state)); - } - }, - state: state, - cancellationToken: CancellationToken.None - ); + return; } + + Task.Delay(timeout * 1000, cancellationToken).ContinueWith( + (task, innerState) => + { + if (!task.IsCanceled && !completion.Task.IsCompleted) + { + completion.TrySetException(onFailure(innerState)); + } + }, + state: state, + cancellationToken: CancellationToken.None); } -#endif } internal static class SQL @@ -520,7 +496,7 @@ static internal Exception UserInstanceFailoverNotCompatible() } static internal Exception CredentialsNotProvided(SqlAuthenticationMethod auth) { - return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_CredentialsNotProvided, DbConnectionStringBuilderUtil.AuthenticationTypeToString(auth))); + return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_CredentialsNotProvided, DbConnectionStringUtilities.AuthenticationTypeToString(auth))); } static internal Exception InvalidCertAuth() { @@ -1012,6 +988,16 @@ internal static Exception TextReaderNotSupportOnColumnType(string columnName) return ADP.InvalidCast(StringsHelper.GetString(Strings.SQL_TextReaderNotSupportOnColumnType, columnName)); } + internal static Exception VectorNotSupportedOnColumnType(string columnName) + { + return ADP.InvalidCast(StringsHelper.GetString(Strings.SQL_VectorNotSupportedOnColumnType, columnName)); + } + + internal static Exception VectorTypeNotSupported(string value) + { + return ADP.NotSupported(StringsHelper.GetString(Strings.SQL_VectorTypeNotSupported, value)); + } + internal static Exception XmlReaderNotSupportOnColumnType(string columnName) { return ADP.InvalidCast(StringsHelper.GetString(Strings.SQL_XmlReaderNotSupportOnColumnType, columnName)); @@ -1285,6 +1271,10 @@ internal static Exception BulkLoadNonMatchingColumnName(string columnName) { return BulkLoadNonMatchingColumnName(columnName, null); } + internal static Exception BulkLoadNonMatchingColumnNames(IEnumerable columnNames) + { + return BulkLoadNonMatchingColumnName(string.Join(",", columnNames), null); + } internal static Exception BulkLoadNonMatchingColumnName(string columnName, Exception e) { return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_BulkLoadNonMatchingColumnName, columnName), e); @@ -1548,7 +1538,7 @@ internal static SqlException CR_NoCRAckAtReconnection(SqlInternalConnectionTds i internal static SqlException CR_TDSVersionNotPreserved(SqlInternalConnectionTds internalConnection) { SqlErrorCollection errors = new SqlErrorCollection(); - errors.Add(new SqlError(0, 0, TdsEnums.FATAL_ERROR_CLASS, null, StringsHelper.GetString(Strings.SQLCR_TDSVestionNotPreserved), "", 0)); + errors.Add(new SqlError(0, 0, TdsEnums.FATAL_ERROR_CLASS, null, StringsHelper.GetString(Strings.SQLCR_TDSVersionNotPreserved), "", 0)); SqlException exc = SqlException.CreateException(errors, "", internalConnection, innerException: null, batchCommand: null); return exc; } @@ -2458,6 +2448,7 @@ static internal Exception NotificationsNotAvailableOnContextConnection() return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_NotificationsNotAvailableOnContextConnection)); } + // @TODO: Check these methods for usage static internal Exception UserInstanceNotAvailableInProc() { return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_UserInstanceNotAvailableInProc)); @@ -2490,9 +2481,6 @@ internal static string GetSNIErrorMessage(uint sniError) return StringsHelper.GetResourceString(errorMessageId); } - // BulkLoad - internal const string WriteToServer = "WriteToServer"; - // Default values for SqlDependency and SqlNotificationRequest internal const int SqlDependencyTimeoutDefault = 0; internal const int SqlDependencyServerTimeout = 5 * 24 * 3600; // 5 days - used to compute default TTL of the dependency @@ -2531,11 +2519,6 @@ static internal Exception SnapshotNotSupported(System.Data.IsolationLevel level) { return ADP.Argument(StringsHelper.GetString(Strings.SQL_SnapshotNotSupported, typeof(System.Data.IsolationLevel), level.ToString())); } - static internal Exception UnexpectedSmiEvent(Microsoft.Data.SqlClient.Server.SmiEventSink_Default.UnexpectedEventType eventType) - { - Debug.Assert(false, "UnexpectedSmiEvent: " + eventType.ToString()); // Assert here, because these exceptions will most likely be eaten by the server. - return ADP.InvalidOperation(StringsHelper.GetString(Strings.SQL_UnexpectedSmiEvent, (int)eventType)); - } #endif } @@ -2687,7 +2670,7 @@ internal static class SqlServerEscapeHelper /// escapes the name with [], also escapes the last close bracket with double-bracket internal static string EscapeIdentifier(string name) { - Debug.Assert(!ADP.IsEmpty(name), "null or empty identifiers are not allowed"); + Debug.Assert(!string.IsNullOrEmpty(name), "null or empty identifiers are not allowed"); return "[" + name.Replace("]", "]]") + "]"; } @@ -2697,7 +2680,7 @@ internal static string EscapeIdentifier(string name) internal static void EscapeIdentifier(StringBuilder builder, string name) { Debug.Assert(builder != null, "builder cannot be null"); - Debug.Assert(!ADP.IsEmpty(name), "null or empty identifiers are not allowed"); + Debug.Assert(!string.IsNullOrEmpty(name), "null or empty identifiers are not allowed"); builder.Append("["); builder.Append(name.Replace("]", "]]")); @@ -2722,7 +2705,7 @@ internal static string EscapeStringAsLiteral(string input) /// escaped and quoted literal string internal static string MakeStringLiteral(string input) { - if (ADP.IsEmpty(input)) + if (string.IsNullOrEmpty(input)) { return "''"; } @@ -2784,11 +2767,5 @@ public static MethodInfo GetPromotedToken } } } - - internal static class InOutOfProcHelper - { - internal static bool InProc - => false; - } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs index 5ab7560718..e234fbbef6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsEnums.cs @@ -4,6 +4,7 @@ using System; using System.Data; +using Microsoft.Data.Common.ConnectionString; namespace Microsoft.Data.SqlClient { @@ -14,7 +15,7 @@ internal static class TdsEnums // internal tdsparser constants - public const string SQL_PROVIDER_NAME = Common.DbConnectionStringDefaults.ApplicationName; + public const string SQL_PROVIDER_NAME = DbConnectionStringDefaults.ApplicationName; public static readonly decimal SQL_SMALL_MONEY_MIN = new(-214748.3648); public static readonly decimal SQL_SMALL_MONEY_MAX = new(214748.3647); @@ -239,6 +240,7 @@ public enum EnvChangeType : byte public const byte FEATUREEXT_UTF8SUPPORT = 0x0A; public const byte FEATUREEXT_SQLDNSCACHING = 0x0B; public const byte FEATUREEXT_JSONSUPPORT = 0x0D; + public const byte FEATUREEXT_VECTORSUPPORT = 0x0E; [Flags] public enum FeatureExtension : uint @@ -252,7 +254,8 @@ public enum FeatureExtension : uint DataClassification = 1 << (TdsEnums.FEATUREEXT_DATACLASSIFICATION - 1), UTF8Support = 1 << (TdsEnums.FEATUREEXT_UTF8SUPPORT - 1), SQLDNSCaching = 1 << (TdsEnums.FEATUREEXT_SQLDNSCACHING - 1), - JsonSupport = 1 << (TdsEnums.FEATUREEXT_JSONSUPPORT - 1) + JsonSupport = 1 << (TdsEnums.FEATUREEXT_JSONSUPPORT - 1), + VectorSupport = 1 << (TdsEnums.FEATUREEXT_VECTORSUPPORT - 1) } public const uint UTF8_IN_TDSCOLLATION = 0x4000000; @@ -344,29 +347,22 @@ public enum ActiveDirectoryWorkflow : byte 0x72xx0002 -> 2005 RTM */ - // Pre 2000 SP1 versioning scheme: - public const int SQL70OR2000_MAJOR = 0x07; // The high byte (b3) is not sufficient to distinguish - public const int SQL70_INCREMENT = 0x00; // 7.0 and 2000 - public const int SQL2000_INCREMENT = 0x01; // So we need to look at the high-mid byte (b2) as well - public const int DEFAULT_MINOR = 0x0000; - // Majors: - public const int SQL2000SP1_MAJOR = 0x71; // For 2000 SP1 and later the versioning schema changed and - public const int SQL2005_MAJOR = 0x72; // the high-byte is sufficient to distinguish later versions + // For 2000 SP1 and later the versioning schema changed and + // the high-byte is sufficient to distinguish later versions + public const int SQL2005_MAJOR = 0x72; public const int SQL2008_MAJOR = 0x73; public const int SQL2012_MAJOR = 0x74; public const int TDS8_MAJOR = 0x08; // TDS8 version to be used at login7 public const string TDS8_Protocol = "tds/8.0"; //TDS8 // Increments: - public const int SQL2000SP1_INCREMENT = 0x00; public const int SQL2005_INCREMENT = 0x09; public const int SQL2008_INCREMENT = 0x0b; public const int SQL2012_INCREMENT = 0x00; public const int TDS8_INCREMENT = 0x00; // Minors: - public const int SQL2000SP1_MINOR = 0x0001; public const int SQL2005_RTM_MINOR = 0x0002; public const int SQL2008_MINOR = 0x0003; public const int SQL2012_MINOR = 0x0004; @@ -491,6 +487,7 @@ public enum ActiveDirectoryWorkflow : byte public const int SQLDATETIMEOFFSET = 0x2b; public const int SQLJSON = 0xF4; + public const int SQLVECTOR = 0xF5; public const int DEFAULT_VARTIME_SCALE = 7; @@ -551,22 +548,7 @@ public enum ActiveDirectoryWorkflow : byte public const ushort RPC_PROCID_PREPEXECRPC = 14; public const ushort RPC_PROCID_UNPREPARE = 15; - // For Transactions - public const string TRANS_BEGIN = "BEGIN TRANSACTION"; - public const string TRANS_COMMIT = "COMMIT TRANSACTION"; - public const string TRANS_ROLLBACK = "ROLLBACK TRANSACTION"; - public const string TRANS_IF_ROLLBACK = "IF @@TRANCOUNT > 0 ROLLBACK TRANSACTION"; - public const string TRANS_SAVE = "SAVE TRANSACTION"; - - // For Transactions - isolation levels - public const string TRANS_READ_COMMITTED = "SET TRANSACTION ISOLATION LEVEL READ COMMITTED"; - public const string TRANS_READ_UNCOMMITTED = "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED"; - public const string TRANS_REPEATABLE_READ = "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ"; - public const string TRANS_SERIALIZABLE = "SET TRANSACTION ISOLATION LEVEL SERIALIZABLE"; - public const string TRANS_SNAPSHOT = "SET TRANSACTION ISOLATION LEVEL SNAPSHOT"; - - // Batch RPC flags - public const byte SQL2000_RPCBATCHFLAG = 0x80; + // Batch RPC flag public const byte SQL2005_RPCBATCHFLAG = 0xFF; // RPC flags @@ -622,7 +604,7 @@ public enum ActiveDirectoryWorkflow : byte public const uint SNI_UNINITIALIZED = unchecked((uint)-1); public const uint SNI_SUCCESS = 0; // The operation completed successfully. public const uint SNI_ERROR = 1; // Error - public const uint SNI_WAIT_TIMEOUT = 258; // The wait operation timed out. + public const int SNI_WAIT_TIMEOUT = 258; // The wait operation timed out. public const uint SNI_SUCCESS_IO_PENDING = 997; // Overlapped I/O operation is in progress. // Windows Sockets Error Codes @@ -999,6 +981,10 @@ internal enum FedAuthInfoId : byte // JSON Support constants internal const byte MAX_SUPPORTED_JSON_VERSION = 0x01; + // Vector Support constants + internal const byte MAX_SUPPORTED_VECTOR_VERSION = 0x01; + internal const int VECTOR_HEADER_SIZE = 8; + // TCE Related constants internal const byte MAX_SUPPORTED_TCE_VERSION = 0x03; // max version internal const byte MIN_TCE_VERSION_WITH_ENCLAVE_SUPPORT = 0x02; // min version with enclave support diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParameterSetter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParameterSetter.cs index 13446c66ce..9f0bd70c9c 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParameterSetter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParameterSetter.cs @@ -27,27 +27,20 @@ internal TdsParameterSetter(TdsParserStateObject stateObj, SmiMetaData md) #endregion #region TypedGetterSetter overrides - // Are calls to Get methods allowed? - internal override bool CanGet - { - get - { - return false; - } - } - // Are calls to Set methods allowed? - internal override bool CanSet - { - get - { - return true; - } - } + /// + /// Are calls to Get methods allowed? + /// + protected override bool CanGet => false; + + /// + /// Are calls to Set methods allowed? + /// + protected override bool CanSet => true; // valid for structured types // This method called for both get and set. - internal override SmiTypedGetterSetter GetTypedGetterSetter(SmiEventSink sink, int ordinal) + internal override SmiTypedGetterSetter GetTypedGetterSetter(int ordinal) { Debug.Assert(0 == ordinal, "TdsParameterSetter only supports 0 for ordinal. Actual = " + ordinal); return _target; @@ -55,11 +48,10 @@ internal override SmiTypedGetterSetter GetTypedGetterSetter(SmiEventSink sink, i // Set value to null // valid for all types - public override void SetDBNull(SmiEventSink sink, int ordinal) + public override void SetDBNull(int ordinal) { Debug.Assert(0 == ordinal, "TdsParameterSetter only supports 0 for ordinal. Actual = " + ordinal); - - _target.EndElements(sink); + _target.EndElements(); } #endregion } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs index 12a67c4654..6226f958a5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -1,43 +1,59 @@ -using System; +using System; using System.Buffers; using System.Diagnostics; +using System.Text; +using Microsoft.Data.SqlClient.Utilities; #nullable enable namespace Microsoft.Data.SqlClient { + internal partial class TdsParser { + private static readonly Encoding s_utf8EncodingWithoutBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); + internal void ProcessSSPI(int receivedLength) { Debug.Assert(_authenticationProvider is not null); SniContext outerContext = _physicalStateObj.SniContext; _physicalStateObj.SniContext = SniContext.Snix_ProcessSspi; + // allocate received buffer based on length from SSPI message byte[] receivedBuff = ArrayPool.Shared.Rent(receivedLength); - // read SSPI data received from server - Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); - TdsOperationStatus result = _physicalStateObj.TryReadByteArray(receivedBuff, receivedLength); - if (result != TdsOperationStatus.Done) + try { - throw SQL.SynchronousCallMayNotPend(); - } + // read SSPI data received from server + Debug.Assert(_physicalStateObj._syncOverAsync, "Should not attempt pends in a synchronous call"); + TdsOperationStatus result = _physicalStateObj.TryReadByteArray(receivedBuff, receivedLength); + if (result != TdsOperationStatus.Done) + { + throw SQL.SynchronousCallMayNotPend(); + } - // allocate send buffer and initialize length - byte[] rentedSendBuff = ArrayPool.Shared.Rent((int)_authenticationProvider!.MaxSSPILength); - byte[] sendBuff = rentedSendBuff; // need to track these separately in case someone updates the ref parameter - uint sendLength = _authenticationProvider.MaxSSPILength; + // allocate send buffer and initialize length + var writer = ObjectPools.BufferWriter.Rent(); - // make call for SSPI data - _authenticationProvider.SSPIData(receivedBuff.AsMemory(0, receivedLength), ref sendBuff, ref sendLength, _sniSpnBuffer); + try + { + // make call for SSPI data + _authenticationProvider!.WriteSSPIContext(receivedBuff.AsSpan(0, receivedLength), writer); - // DO NOT SEND LENGTH - TDS DOC INCORRECT! JUST SEND SSPI DATA! - _physicalStateObj.WriteByteArray(sendBuff, (int)sendLength, 0); + // DO NOT SEND LENGTH - TDS DOC INCORRECT! JUST SEND SSPI DATA! + _physicalStateObj.WriteByteSpan(writer.WrittenSpan); - ArrayPool.Shared.Return(rentedSendBuff, clearArray: true); - ArrayPool.Shared.Return(receivedBuff, clearArray: true); + } + finally + { + ObjectPools.BufferWriter.Return(writer); + } + } + finally + { + ArrayPool.Shared.Return(receivedBuff, clearArray: true); + } // set message type so server knows its a SSPI response _physicalStateObj._outputMessageType = TdsEnums.MT_SSPI; @@ -139,72 +155,67 @@ internal void TdsLogin( } // allocate memory for SSPI variables - byte[] rentedSSPIBuff = null; - byte[] outSSPIBuff = null; // track the rented buffer as a separate variable in case it is updated via the ref parameter - uint outSSPILength = 0; + ArrayBufferWriter sspiWriter = null; - // only add lengths of password and username if not using SSPI or requesting federated authentication info - if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) + try { - checked + // only add lengths of password and username if not using SSPI or requesting federated authentication info + if (!rec.useSSPI && !(_connHandler._federatedAuthenticationInfoRequested || _connHandler._federatedAuthenticationRequested)) { - length += (userName.Length * 2) + encryptedPasswordLengthInBytes - + encryptedChangePasswordLengthInBytes; + checked + { + length += (userName.Length * 2) + encryptedPasswordLengthInBytes + + encryptedChangePasswordLengthInBytes; + } } - } - else - { - if (rec.useSSPI) + else { - // now allocate proper length of buffer, and set length - outSSPILength = _authenticationProvider.MaxSSPILength; - rentedSSPIBuff = ArrayPool.Shared.Rent((int)outSSPILength); - outSSPIBuff = rentedSSPIBuff; - - // Call helper function for SSPI data and actual length. - // Since we don't have SSPI data from the server, send null for the - // byte[] buffer and 0 for the int length. - Debug.Assert(SniContext.Snix_Login == _physicalStateObj.SniContext, $"Unexpected SniContext. Expecting Snix_Login, actual value is '{_physicalStateObj.SniContext}'"); - _physicalStateObj.SniContext = SniContext.Snix_LoginSspi; - _authenticationProvider.SSPIData(ReadOnlyMemory.Empty, ref outSSPIBuff, ref outSSPILength, _sniSpnBuffer); - - if (outSSPILength > int.MaxValue) + if (rec.useSSPI) { - throw SQL.InvalidSSPIPacketSize(); // SqlBu 332503 - } - _physicalStateObj.SniContext = SniContext.Snix_Login; + sspiWriter = ObjectPools.BufferWriter.Rent(); - checked - { - length += (int)outSSPILength; + // Call helper function for SSPI data and actual length. + // Since we don't have SSPI data from the server, send null for the + // byte[] buffer and 0 for the int length. + Debug.Assert(SniContext.Snix_Login == _physicalStateObj.SniContext, $"Unexpected SniContext. Expecting Snix_Login, actual value is '{_physicalStateObj.SniContext}'"); + _physicalStateObj.SniContext = SniContext.Snix_LoginSspi; + _authenticationProvider.WriteSSPIContext(ReadOnlySpan.Empty, sspiWriter); + + _physicalStateObj.SniContext = SniContext.Snix_Login; + + checked + { + length += (int)sspiWriter.WrittenCount; + } } } - } - int feOffset = length; - // calculate and reserve the required bytes for the featureEx - length = ApplyFeatureExData(requestedFeatures, recoverySessionData, fedAuthFeatureExtensionData, useFeatureExt, length); - - WriteLoginData(rec, - requestedFeatures, - recoverySessionData, - fedAuthFeatureExtensionData, - encrypt, - encryptedPassword, - encryptedChangePassword, - encryptedPasswordLengthInBytes, - encryptedChangePasswordLengthInBytes, - useFeatureExt, - userName, - length, - feOffset, - clientInterfaceName, - outSSPIBuff, - outSSPILength); - - if (rentedSSPIBuff != null) + int feOffset = length; + // calculate and reserve the required bytes for the featureEx + length = ApplyFeatureExData(requestedFeatures, recoverySessionData, fedAuthFeatureExtensionData, useFeatureExt, length); + + WriteLoginData(rec, + requestedFeatures, + recoverySessionData, + fedAuthFeatureExtensionData, + encrypt, + encryptedPassword, + encryptedChangePassword, + encryptedPasswordLengthInBytes, + encryptedChangePasswordLengthInBytes, + useFeatureExt, + userName, + length, + feOffset, + clientInterfaceName, + sspiWriter is { } ? sspiWriter.WrittenSpan : ReadOnlySpan.Empty); + } + finally { - ArrayPool.Shared.Return(rentedSSPIBuff, clearArray: true); + if (sspiWriter is not null) + { + ObjectPools.BufferWriter.Return(sspiWriter); + } } _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH); diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.stub.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.stub.cs new file mode 100644 index 0000000000..27bd25a989 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParser.stub.cs @@ -0,0 +1,109 @@ +// 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. + +// @TODO: This is only a stub partial for clearing errors while merging other files. + +using System; +using System.Data.SqlTypes; +using System.Threading.Tasks; +using Microsoft.Data.Sql; + +namespace Microsoft.Data.SqlClient +{ + internal partial class TdsParser + { + internal bool _asyncWrite = false; + + internal SqlInternalConnectionTds Connection { get; set; } + + internal SqlInternalTransaction CurrentTransaction { get; set; } + + internal TdsParserState State { get; set; } + + internal static SqlDecimal AdjustSqlDecimalScale(SqlDecimal sqlValue, int scale) => + SqlDecimal.Null; + + internal object EncryptColumnValue( + object value, + SqlMetaDataPriv metadata, + string column, + TdsParserStateObject stateObj, + bool isDataFeed, + bool isSqlType) => + null; + + internal TdsParserStateObject GetSession(object owner) => + null; + + internal void LoadColumnEncryptionKeys( + _SqlMetaDataSet metadataCollection, + SqlConnection connection, + SqlCommand command = null) + { + } + + internal void Run( + RunBehavior runBehavior, + SqlCommand cmdHandler, + SqlDataReader dataStream, + BulkCopySimpleResultSet bulkCopyHandler, + TdsParserStateObject stateObj) + { + } + + #if NETFRAMEWORK + internal void RunReliably( + RunBehavior runBehavior, + SqlCommand cmdHandler, + SqlDataReader dataStream, + BulkCopySimpleResultSet bulkCopyHandler, + TdsParserStateObject stateObj) + { + } + #endif + + internal Task TdsExecuteSQLBatch( + string text, + int timeout, + SqlNotificationRequest notificationRequest, + TdsParserStateObject stateObj, + bool sync, + bool callerHasConnectionLock = false, + byte[] enclavePackage = null) => + null; + + internal bool ShouldEncryptValuesForBulkCopy() => + false; + + internal Task WriteBulkCopyDone(TdsParserStateObject stateObj) => + Task.CompletedTask; + + internal void WriteBulkCopyMetaData( + _SqlMetaDataSet metadataCollection, + int count, + TdsParserStateObject stateObj) + { + } + + internal Task WriteBulkCopyValue( + object value, + SqlMetaDataPriv metadata, + TdsParserStateObject stateObj, + bool isSqlType, + bool isDataFeed, + bool isNull) => + Task.CompletedTask; + + internal Task WriteSqlVariantDataRowValue(object value, TdsParserStateObject stateObje) => + Task.CompletedTask; + + internal void WriteSqlVariantDate(DateTime value, TdsParserStateObject stateObj) + { + } + + internal void WriteSqlVariantDateTime2(DateTime value, TdsParserStateObject stateObj) + { + } + } +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs index 3f4186186b..873d87c92e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs @@ -30,8 +30,8 @@ internal enum EncryptionOptions NOT_SUP, REQ, LOGIN, -#if NETFRAMEWORK OPTIONS_MASK = 0x3f, +#if NETFRAMEWORK CTAIP = 0x40, CLIENT_CERT = 0x80, #endif @@ -125,11 +125,10 @@ internal sealed class SqlLoginAck internal byte minorVersion; internal short buildNum; internal uint tdsVersion; -#if NETFRAMEWORK - internal string programName; - internal bool isVersion8; -#endif + #if NETFRAMEWORK + internal string programName; + #endif } internal sealed class SqlFedAuthInfo @@ -305,9 +304,7 @@ internal sealed partial class _SqlMetaDataSet internal DataTable schemaTable; private readonly _SqlMetaData[] _metaDataArray; -#if !NETFRAMEWORK internal ReadOnlyCollection dbColumnSchema; -#endif private int _hiddenColumnCount; private int[] _visibleColumnMap; @@ -327,11 +324,9 @@ private _SqlMetaDataSet(_SqlMetaDataSet original) id = original.id; _hiddenColumnCount = original._hiddenColumnCount; _visibleColumnMap = original._visibleColumnMap; -#if !NETFRAMEWORK dbColumnSchema = original.dbColumnSchema; -#else schemaTable = original.schemaTable; -#endif + if (original._metaDataArray == null) { _metaDataArray = null; @@ -752,7 +747,7 @@ private void ParseMultipartName() } } - internal static readonly MultiPartTableName Null = new MultiPartTableName(new string[] { null, null, null, null }); + internal static readonly MultiPartTableName Null = new(new string[] { null, null, null, null }); } internal static class SslProtocolsHelper @@ -767,35 +762,37 @@ private static string ToFriendlyName(this SslProtocols protocol) { name = "TLS 1.3"; }*/ +#pragma warning disable CA5397 // Do not use deprecated SslProtocols values #pragma warning disable CA5398 // Avoid hardcoded SslProtocols values - if ((protocol & SslProtocols.Tls12) == SslProtocols.Tls12) + if ((protocol & SslProtocols.Tls12) != SslProtocols.None) { name = "TLS 1.2"; } -#if NET +#if NET8_0_OR_GREATER #pragma warning disable SYSLIB0039 // Type or member is obsolete: TLS 1.0 & 1.1 are deprecated #endif - else if ((protocol & SslProtocols.Tls11) == SslProtocols.Tls11) + else if ((protocol & SslProtocols.Tls11) != SslProtocols.None) { name = "TLS 1.1"; } - else if ((protocol & SslProtocols.Tls) == SslProtocols.Tls) + else if ((protocol & SslProtocols.Tls) != SslProtocols.None) { name = "TLS 1.0"; } -#if NET +#if NET8_0_OR_GREATER #pragma warning restore SYSLIB0039 // Type or member is obsolete: SSL and TLS 1.0 & 1.1 is deprecated #endif #pragma warning disable CS0618 // Type or member is obsolete: SSL is deprecated - else if ((protocol & SslProtocols.Ssl3) == SslProtocols.Ssl3) + else if ((protocol & SslProtocols.Ssl3) != SslProtocols.None) { name = "SSL 3.0"; } - else if ((protocol & SslProtocols.Ssl2) == SslProtocols.Ssl2) + else if ((protocol & SslProtocols.Ssl2) != SslProtocols.None) #pragma warning restore CS0618 // Type or member is obsolete: SSL and TLS 1.0 & 1.1 is deprecated { name = "SSL 2.0"; } +#pragma warning restore CA5397 // Do not use deprecated SslProtocols values #pragma warning restore CA5398 // Avoid hardcoded SslProtocols values else { @@ -817,17 +814,17 @@ private static string ToFriendlyName(this SslProtocols protocol) public static string GetProtocolWarning(this SslProtocols protocol) { string message = string.Empty; -#if NET +#if NET8_0_OR_GREATER #pragma warning disable SYSLIB0039 // Type or member is obsolete: TLS 1.0 & 1.1 are deprecated #endif #pragma warning disable CS0618 // Type or member is obsolete : SSL is deprecated #pragma warning disable CA5397 // Do not use deprecated SslProtocols values -#pragma warning disable CA5398 // Do not use deprecated SslProtocols values +#pragma warning disable CA5398 // Avoid hardcoded SslProtocols values if ((protocol & (SslProtocols.Ssl2 | SslProtocols.Ssl3 | SslProtocols.Tls | SslProtocols.Tls11)) != SslProtocols.None) -#pragma warning restore CA5398 // Do not use deprecated SslProtocols values +#pragma warning restore CA5398 // Avoid hardcoded SslProtocols values #pragma warning restore CA5397 // Do not use deprecated SslProtocols values #pragma warning restore CS0618 // Type or member is obsolete : SSL is deprecated -#if NET +#if NET8_0_OR_GREATER #pragma warning restore SYSLIB0039 // Type or member is obsolete: SSL and TLS 1.0 & 1.1 is deprecated #endif { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.Windows.cs index 3b1b668733..37dc781dbc 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserSafeHandles.Windows.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; using Interop.Windows.Sni; +using Microsoft.Data.SqlClient.LocalDb; #if NETFRAMEWORK using System.Runtime.CompilerServices; @@ -35,7 +36,7 @@ private SNILoadHandle() : base(IntPtr.Zero, true) { } finally { - _sniStatus = SNINativeMethodWrapper.SNIInitialize(); + _sniStatus = SniNativeWrapper.SniInitialize(); base.handle = (IntPtr)1; // Initialize to non-zero dummy variable. } } @@ -56,7 +57,7 @@ public bool ClientOSEncryptionSupport { uint value = 0; // Query OS to find out whether encryption is supported. - SNINativeMethodWrapper.SNIQueryInfo(QueryType.SNI_QUERY_CLIENT_ENCRYPT_POSSIBLE, ref value); + SniNativeWrapper.SniQueryInfo(QueryType.SNI_QUERY_CLIENT_ENCRYPT_POSSIBLE, ref value); _clientOSEncryptionSupport = value != 0; } catch (Exception e) @@ -77,8 +78,8 @@ override protected bool ReleaseHandle() { if (TdsEnums.SNI_SUCCESS == _sniStatus) { - LocalDBAPI.ReleaseDLLHandles(); - SNINativeMethodWrapper.SNITerminate(); + LocalDbApi.ReleaseDllHandles(); + SniNativeWrapper.SniTerminate(); } base.handle = IntPtr.Zero; } @@ -106,11 +107,7 @@ private static void ReadDispatcher(IntPtr key, IntPtr packet, uint error) if (stateObj != null) { -#if NETFRAMEWORK - stateObj.ReadAsyncCallback(IntPtr.Zero, packet, error); -#else stateObj.ReadAsyncCallback(IntPtr.Zero, PacketHandle.FromNativePointer(packet), error); -#endif // NETFRAMEWORK } } } @@ -131,11 +128,7 @@ private static void WriteDispatcher(IntPtr key, IntPtr packet, uint error) if (stateObj != null) { -#if NETFRAMEWORK - stateObj.WriteAsyncCallback(IntPtr.Zero, packet, error); -#else stateObj.WriteAsyncCallback(IntPtr.Zero, PacketHandle.FromNativePointer(packet), error); -#endif // NETFRAMEWORK } } } @@ -150,7 +143,7 @@ internal sealed class SNIHandle : SafeHandle internal SNIHandle( ConsumerInfo myInfo, string serverName, - byte[] spnBuffer, + ref string spn, int timeout, out byte[] instanceName, bool flushCache, @@ -183,15 +176,38 @@ internal SNIHandle( // timeout = Timeout.Infinite; // -1 == native SNIOPEN_TIMEOUT_VALUE / INFINITE //} -#if NETFRAMEWORK + #if NETFRAMEWORK int transparentNetworkResolutionStateNo = (int)transparentNetworkResolutionState; - _status = SNINativeMethodWrapper.SNIOpenSyncEx(myInfo, serverName, ref base.handle, - spnBuffer, instanceName, flushCache, fSync, timeout, fParallel, transparentNetworkResolutionStateNo, totalTimeout, - ADP.IsAzureSqlServerEndpoint(serverName), ipPreference, cachedDNSInfo, hostNameInCertificate); -#else - _status = SNINativeMethodWrapper.SNIOpenSyncEx(myInfo, serverName, ref base.handle, - spnBuffer, instanceName, flushCache, fSync, timeout, fParallel, ipPreference, cachedDNSInfo, hostNameInCertificate); -#endif // NETFRAMEWORK + _status = SniNativeWrapper.SniOpenSyncEx( + myInfo, + serverName, + ref base.handle, + ref spn, + instanceName, + flushCache, + fSync, + timeout, + fParallel, + transparentNetworkResolutionStateNo, + totalTimeout, + ipPreference, + cachedDNSInfo, + hostNameInCertificate); + #else + _status = SniNativeWrapper.SniOpenSyncEx( + myInfo, + serverName, + ref base.handle, + ref spn, + instanceName, + flushCache, + fSync, + timeout, + fParallel, + ipPreference, + cachedDNSInfo, + hostNameInCertificate); + #endif } } @@ -202,7 +218,7 @@ internal SNIHandle(ConsumerInfo myInfo, SNIHandle parent, SqlConnectionIPAddress { } finally { - _status = SNINativeMethodWrapper.SNIOpenMarsSession(myInfo, parent, ref base.handle, parent._fSync, ipPreference, cachedDNSInfo); + _status = SniNativeWrapper.SniOpenMarsSession(myInfo, parent, ref base.handle, parent._fSync, ipPreference, cachedDNSInfo); } } @@ -221,7 +237,7 @@ override protected bool ReleaseHandle() base.handle = IntPtr.Zero; if (IntPtr.Zero != ptr) { - if (0 != SNINativeMethodWrapper.SNIClose(ptr)) + if (0 != SniNativeWrapper.SniClose(ptr)) { return false; // SNIClose should never fail. } @@ -242,7 +258,7 @@ internal sealed class SNIPacket : SafeHandle { internal SNIPacket(SafeHandle sniHandle) : base(IntPtr.Zero, true) { - SNINativeMethodWrapper.SNIPacketAllocate(sniHandle, IoType.WRITE, ref base.handle); + SniNativeWrapper.SniPacketAllocate(sniHandle, IoType.WRITE, ref base.handle); if (IntPtr.Zero == base.handle) { throw SQL.SNIPacketAllocationFailure(); @@ -264,7 +280,7 @@ override protected bool ReleaseHandle() base.handle = IntPtr.Zero; if (IntPtr.Zero != ptr) { - SNINativeMethodWrapper.SNIPacketRelease(ptr); + SniNativeWrapper.SniPacketRelease(ptr); } return true; } @@ -288,7 +304,7 @@ public SNIPacket Take(SNIHandle sniHandle) { // Success - reset the packet packet = _packets.Pop(); - SNINativeMethodWrapper.SNIPacketReset(sniHandle, IoType.WRITE, packet, ConsumerNumber.SNI_Consumer_SNI); + SniNativeWrapper.SniPacketReset(sniHandle, IoType.WRITE, packet, ConsumerNumber.SNI_Consumer_SNI); } else { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs index b3cf628594..da0e06cc14 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObject.cs @@ -11,11 +11,13 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Common; +#if NETFRAMEWORK +using System.Runtime.ConstrainedExecution; +#endif namespace Microsoft.Data.SqlClient { #if NETFRAMEWORK - using PacketHandle = IntPtr; using RuntimeHelpers = System.Runtime.CompilerServices.RuntimeHelpers; #endif @@ -31,8 +33,10 @@ internal enum TdsOperationStatus : int InvalidData = 2 } - partial class TdsParserStateObject + internal abstract partial class TdsParserStateObject { + private static readonly ContextCallback s_readAsyncCallbackComplete = ReadAsyncCallbackComplete; + private static int s_objectTypeCount; // EventSource counter internal readonly int _objectID = Interlocked.Increment(ref s_objectTypeCount); @@ -47,6 +51,28 @@ internal enum SnapshottedStateFlags : byte AttentionReceived = 1 << 5 // NOTE: Received is not volatile as it is only ever accessed\modified by TryRun its callees (i.e. single threaded access) } + internal readonly struct SniErrorDetails + { + public readonly string ErrorMessage; + public readonly int NativeError; + public readonly uint SniErrorNumber; + public readonly int Provider; + public readonly uint LineNumber; + public readonly string Function; + public readonly Exception Exception; + + internal SniErrorDetails(string errorMessage, int nativeError, uint sniErrorNumber, int provider, uint lineNumber, string function, Exception exception = null) + { + ErrorMessage = errorMessage; + NativeError = nativeError; + SniErrorNumber = sniErrorNumber; + Provider = provider; + LineNumber = lineNumber; + Function = function; + Exception = exception; + } + } + private sealed class TimeoutState { public const int Stopped = 0; @@ -146,6 +172,7 @@ public TimeoutState(int value) internal volatile bool _attentionSent; // true if we sent an Attention to the server internal volatile bool _attentionSending; private readonly TimerCallback _onTimeoutAsync; + private readonly WeakReference _cancellationOwner = new WeakReference(null); // Below 2 properties are used to enforce timeout delays in code to // reproduce issues related to theadpool starvation and timeout delay. @@ -295,7 +322,7 @@ public TimeoutState(int value) // Constructors // ////////////////// - internal TdsParserStateObject(TdsParser parser) + protected TdsParserStateObject(TdsParser parser) { // Construct a physical connection Debug.Assert(parser != null, "no parser?"); @@ -329,6 +356,10 @@ private bool GetSnapshottedState(SnapshottedStateFlags flag) return (_snapshottedState & flag) == flag; } + //////////////// + // Properties // + //////////////// + internal bool HasOpenResult { get => GetSnapshottedState(SnapshottedStateFlags.OpenResult); @@ -359,10 +390,6 @@ internal bool HasReceivedColumnMetadata set => SetSnapshottedState(SnapshottedStateFlags.ColMetaDataReceived, value); } - //////////////// - // Properties // - //////////////// - internal int ObjectID => _objectID; // BcpLock - use to lock this object if there is a potential risk of using this object @@ -457,6 +484,56 @@ internal long TimeoutTime } } + //////////////// + // Properties // + //////////////// + + internal abstract uint Status { get; } + + internal abstract Guid? SessionId { get; } + + internal abstract SessionHandle SessionHandle { get; } + + protected abstract PacketHandle EmptyReadPacket { get; } + + internal abstract PacketHandle GetResetWritePacket(int dataSize); + + protected abstract uint SniPacketGetData(PacketHandle packet, byte[] _inBuff, ref uint dataSize); + + protected abstract bool CheckPacket(PacketHandle packet, TaskCompletionSource source); + + internal abstract bool IsFailedHandle(); + + internal abstract bool IsPacketEmpty(PacketHandle readPacket); + + internal abstract void ReleasePacket(PacketHandle syncReadPacket); + + internal abstract PacketHandle ReadSyncOverAsync(int timeoutRemaining, out uint error); + + internal abstract uint WritePacket(PacketHandle packet, bool sync); + + internal abstract PacketHandle AddPacketToPendingList(PacketHandle packet); + + protected abstract void RemovePacketFromPendingList(PacketHandle pointer); + + internal abstract void ClearAllWritePackets(); + + internal abstract bool IsValidPacket(PacketHandle packetPointer); + + internal abstract PacketHandle ReadAsync(SessionHandle handle, out uint error); + + internal abstract uint SniGetConnectionId(ref Guid clientConnectionId); + + internal abstract uint DisableSsl(); + + internal abstract SspiContextProvider CreateSspiContextProvider(); + + internal abstract uint EnableMars(ref uint info); + + internal abstract uint SetConnectionBufferSize(ref uint unsignedPacketSize); + + internal abstract void DisposePacketCache(); + internal int GetTimeoutRemaining() { int remaining; @@ -667,6 +744,93 @@ internal void Activate(object owner) Debug.Assert(result == 1, "invalid deactivate count"); } + // This method is only called by the command or datareader as a result of a user initiated + // cancel request. + internal void Cancel(object caller) + { + Debug.Assert(caller != null, "Null caller for Cancel!"); + Debug.Assert(caller is SqlCommand || caller is SqlDataReader, "Calling API with invalid caller type: " + caller.GetType()); + + bool hasLock = false; + try + { + // Keep looping until we either grabbed the lock (and therefore sent attention) or the connection closes\breaks + while ((!hasLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) + { + Monitor.TryEnter(this, WaitForCancellationLockPollTimeout, ref hasLock); + if (hasLock) + { // Lock for the time being - since we need to synchronize the attention send. + // This lock is also protecting against concurrent close and async continuations + + // Ensure that, once we have the lock, that we are still the owner + if ((!_cancelled) && (_cancellationOwner.Target == caller)) + { + _cancelled = true; + + if (HasPendingData && !_attentionSent) + { + bool hasParserLock = false; + // Keep looping until we have the parser lock (and so are allowed to write), or the connection closes\breaks + while ((!hasParserLock) && (_parser.State != TdsParserState.Closed) && (_parser.State != TdsParserState.Broken)) + { + try + { + _parser.Connection._parserLock.Wait(canReleaseFromAnyThread: false, timeout: WaitForCancellationLockPollTimeout, lockTaken: ref hasParserLock); + if (hasParserLock) + { + _parser.Connection.ThreadHasParserLockForClose = true; + SendAttention(); + } + } + finally + { + if (hasParserLock) + { + if (_parser.Connection.ThreadHasParserLockForClose) + { + _parser.Connection.ThreadHasParserLockForClose = false; + } + _parser.Connection._parserLock.Release(); + } + } + } + } + } + } + } + } + finally + { + if (hasLock) + { + Monitor.Exit(this); + } + } + } + + private void ResetCancelAndProcessAttention() + { + // This method is shared by CloseSession initiated by DataReader.Close or completed + // command execution, as well as the session reclamation code for cases where the + // DataReader is opened and then GC'ed. + lock (this) + { + // Reset cancel state. + _cancelled = false; + _cancellationOwner.Target = null; + + if (_attentionSent) + { + // Make sure we're cleaning up the AttentionAck if Cancel happened before taking the lock. + // We serialize Cancel/CloseSession to prevent a race condition between these two states. + // The problem is that both sending and receiving attentions are time taking + // operations. + Parser.ProcessPendingAck(this); + } + SetTimeoutStateStopped(); + } + } + // CancelRequest - use to cancel while writing a request to the server // // o none of the request might have been sent to the server, simply reset the buffer, @@ -734,6 +898,11 @@ public void CheckSetResetConnectionState(uint error, CallbackType callbackType) } } + internal void StartSession(object cancellationOwner) + { + _cancellationOwner.Target = cancellationOwner; + } + internal void CloseSession() { ResetCancelAndProcessAttention(); @@ -743,6 +912,17 @@ internal void CloseSession() Parser.PutSession(this); } +#if NETFRAMEWORK + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] +#endif + internal int IncrementPendingCallbacks() + { + int remaining = Interlocked.Increment(ref _pendingCallbacks); + SqlClientEventSource.Log.TryAdvancedTraceEvent("TdsParserStateObject.IncrementPendingCallbacks | ADV | State Object Id {0}, after incrementing _pendingCallbacks: {1}", _objectID, _pendingCallbacks); + Debug.Assert(0 < remaining && remaining <= 3, $"_pendingCallbacks values is invalid after incrementing: {remaining}"); + return remaining; + } + internal bool Deactivate() { bool goodForReuse = false; @@ -870,6 +1050,65 @@ internal void SetTimeoutMilliseconds(long timeout) } } + private void ChangeNetworkPacketTimeout(int dueTime, int period) + { + Timer networkPacketTimeout = _networkPacketTimeout; + if (networkPacketTimeout != null) + { + try + { + networkPacketTimeout.Change(dueTime, period); + } + catch (ObjectDisposedException) + { + // _networkPacketTimeout is set to null before Disposing, but there is still a slight chance + // that object was disposed after we took a copy + } + } + } + + private void ReadAsyncCallbackCaptureException(TaskCompletionSource source) + { + bool captureSuccess = false; + try + { + if (_hasErrorOrWarning) + { + // Do the close on another thread, since we don't want to block the callback thread + ThrowExceptionAndWarning(asyncClose: true); + } + else if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken)) + { + // Connection was closed by another thread before we parsed the packet, so no error was added to the collection + throw ADP.ClosedConnectionError(); + } + } + catch (Exception ex) + { + if (source.TrySetException(ex)) + { + // There was an exception, and it was successfully stored in the task + captureSuccess = true; + } + } + + if (!captureSuccess) + { + // Either there was no exception, or the task was already completed + // This is unusual, but possible if a fatal timeout occurred on another thread (which should mean that the connection is now broken) + Debug.Assert(_parser.State == TdsParserState.Broken || _parser.State == TdsParserState.Closed || _parser.Connection.IsConnectionDoomed, "Failed to capture exception while the connection was still healthy"); + + // The safest thing to do is to ensure that the connection is broken and attempt to cancel the task + // This must be done from another thread to not block the callback thread + Task.Factory.StartNew(() => + { + _parser.State = TdsParserState.Broken; + _parser.Connection.BreakConnection(); + source.TrySetCanceled(); + }); + } + } + internal void ThrowExceptionAndWarning(bool callerHasConnectionLock = false, bool asyncClose = false) { _parser.ThrowExceptionAndWarning(this, null, callerHasConnectionLock, asyncClose); @@ -1018,7 +1257,6 @@ internal void SetBuffer(byte[] buffer, int inBytesUsed, int inBytesRead/*, [Call // NOTE: This method (and all it calls) should be retryable without replaying a snapshot internal TdsOperationStatus TryPrepareBuffer() { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadBuffer"); // you need to setup for a thread abort somewhere before you call this method Debug.Assert(_inBuff != null, "packet buffer should not be null!"); // Header spans packets, or we haven't read the header yet - process header @@ -1112,7 +1350,6 @@ internal bool SetPacketSize(int size) Debug.Assert(_inBuff == null || ( - _parser.Is2005OrNewer && _outBytesUsed == (_outputHeaderLen + BitConverter.ToInt32(_outBuff, _outputHeaderLen)) && _outputPacketNumber == 1) || @@ -1208,7 +1445,6 @@ public TdsOperationStatus TryReadByteArray(Span buff, int len) // Every time you call this method increment the offset and decrease len by the value of totalRead public TdsOperationStatus TryReadByteArray(Span buff, int len, out int totalRead) { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadByteArray"); // you need to setup for a thread abort somewhere before you call this method totalRead = 0; #if DEBUG @@ -1268,7 +1504,6 @@ public TdsOperationStatus TryReadByteArray(Span buff, int len, out int tot // before the byte is returned. internal TdsOperationStatus TryReadByte(out byte value) { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadByte"); // you need to setup for a thread abort somewhere before you call this method Debug.Assert(_inBytesUsed >= 0 && _inBytesUsed <= _inBytesRead, "ERROR - TDSParser: _inBytesUsed < 0 or _inBytesUsed > _inBytesRead"); value = 0; @@ -1375,7 +1610,6 @@ internal TdsOperationStatus TryReadInt16(out short value) internal TdsOperationStatus TryReadInt32(out int value) { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadInt32"); // you need to setup for a thread abort somewhere before you call this method Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); Span buffer = stackalloc byte[4]; if (((_inBytesUsed + 4) > _inBytesRead) || (_inBytesPacket < 4)) @@ -1406,7 +1640,6 @@ internal TdsOperationStatus TryReadInt32(out int value) // This method is safe to call when doing async without snapshot internal TdsOperationStatus TryReadInt64(out long value) { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadInt64"); // you need to setup for a thread abort somewhere before you call this method if ((_inBytesPacket == 0) || (_inBytesUsed == _inBytesRead)) { TdsOperationStatus result = TryPrepareBuffer(); @@ -1488,7 +1721,6 @@ internal TdsOperationStatus TryReadUInt16(out ushort value) // This method is safe to call when doing async without replay internal TdsOperationStatus TryReadUInt32(out uint value) { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadUInt32"); // you need to setup for a thread abort somewhere before you call this method if ((_inBytesPacket == 0) || (_inBytesUsed == _inBytesRead)) { TdsOperationStatus result = TryPrepareBuffer(); @@ -1539,7 +1771,6 @@ internal TdsOperationStatus TryReadUInt32(out uint value) internal TdsOperationStatus TryReadSingle(out float value) { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadSingle"); // you need to setup for a thread abort somewhere before you call this method Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); if (((_inBytesUsed + 4) > _inBytesRead) || (_inBytesPacket < 4)) { @@ -1574,7 +1805,6 @@ internal TdsOperationStatus TryReadSingle(out float value) internal TdsOperationStatus TryReadDouble(out double value) { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadDouble"); // you need to setup for a thread abort somewhere before you call this method Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); if (((_inBytesUsed + 8) > _inBytesRead) || (_inBytesPacket < 8)) { @@ -1609,7 +1839,6 @@ internal TdsOperationStatus TryReadDouble(out double value) internal TdsOperationStatus TryReadString(int length, out string value) { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadString"); // you need to setup for a thread abort somewhere before you call this method Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); int cBytes = length << 1; byte[] buf; @@ -1651,7 +1880,6 @@ internal TdsOperationStatus TryReadString(int length, out string value) internal TdsOperationStatus TryReadStringWithEncoding(int length, System.Text.Encoding encoding, bool isPlp, out string value) { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadStringWithEncoding"); // you need to setup for a thread abort somewhere before you call this method Debug.Assert(_syncOverAsync || !_asyncReadWithoutSnapshot, "This method is not safe to call when doing sync over async"); if (encoding == null) @@ -1982,6 +2210,387 @@ internal TdsOperationStatus TrySkipBytes(int num) return TryReadByteArray(Span.Empty, num); } + ////////////////////////////////////////////// + // Statistics, Tracing, and related methods // + ////////////////////////////////////////////// + + private void SniReadStatisticsAndTracing() + { + SqlStatistics statistics = Parser.Statistics; + if (statistics != null) + { + if (statistics.WaitForReply) + { + statistics.SafeIncrement(ref statistics._serverRoundtrips); + statistics.ReleaseAndUpdateNetworkServerTimer(); + } + + statistics.SafeAdd(ref statistics._bytesReceived, _inBytesRead); + statistics.SafeIncrement(ref statistics._buffersReceived); + } + } + + [Conditional("DEBUG")] + private void AssertValidState() + { + if (_inBytesUsed < 0 || _inBytesRead < 0) + { + Debug.Fail($"Invalid TDS Parser State: either _inBytesUsed or _inBytesRead is negative: {_inBytesUsed}, {_inBytesRead}"); + } + else if (_inBytesUsed > _inBytesRead) + { + Debug.Fail($"Invalid TDS Parser State: _inBytesUsed > _inBytesRead: {_inBytesUsed} > {_inBytesRead}"); + } + + Debug.Assert(_inBytesPacket >= 0, "Packet must not be negative"); + } + + ////////////////////////////////////////////// + // Errors and Warnings // + ////////////////////////////////////////////// + + /// + /// True if there is at least one error or warning (not counting the pre-attention errors\warnings) + /// + internal bool HasErrorOrWarning + { + get + { + return _hasErrorOrWarning; + } + } + + /// + /// Adds an error to the error collection + /// + /// + internal void AddError(SqlError error) + { + Debug.Assert(error != null, "Trying to add a null error"); + + // Switch to sync once we see an error + _syncOverAsync = true; + + lock (_errorAndWarningsLock) + { + _hasErrorOrWarning = true; + if (_errors == null) + { + _errors = new SqlErrorCollection(); + } + _errors.Add(error); + } + } + + /// + /// Gets the number of errors currently in the error collection + /// + internal int ErrorCount + { + get + { + int count = 0; + lock (_errorAndWarningsLock) + { + if (_errors != null) + { + count = _errors.Count; + } + } + return count; + } + } + + /// + /// Adds an warning to the warning collection + /// + /// + internal void AddWarning(SqlError error) + { + Debug.Assert(error != null, "Trying to add a null error"); + + // Switch to sync once we see a warning + _syncOverAsync = true; + + lock (_errorAndWarningsLock) + { + _hasErrorOrWarning = true; + if (_warnings == null) + { + _warnings = new SqlErrorCollection(); + } + _warnings.Add(error); + } + } + + /// + /// Gets the number of warnings currently in the warning collection + /// + internal int WarningCount + { + get + { + int count = 0; + lock (_errorAndWarningsLock) + { + if (_warnings != null) + { + count = _warnings.Count; + } + } + return count; + } + } + + internal int PreAttentionErrorCount + { + get + { + int count = 0; + lock (_errorAndWarningsLock) + { + if (_preAttentionErrors != null) + { + count = _preAttentionErrors.Count; + } + } + return count; + } + } + + /// + /// Gets the number of errors currently in the pre-attention warning collection + /// + internal int PreAttentionWarningCount + { + get + { + int count = 0; + lock (_errorAndWarningsLock) + { + if (_preAttentionWarnings != null) + { + count = _preAttentionWarnings.Count; + } + } + return count; + } + } + + /// + /// Gets the full list of errors and warnings (including the pre-attention ones), then wipes all error and warning lists + /// + /// If true, the connection should be broken + /// An array containing all of the errors and warnings + internal SqlErrorCollection GetFullErrorAndWarningCollection(out bool broken) + { + SqlErrorCollection allErrors = new SqlErrorCollection(); + broken = false; + + lock (_errorAndWarningsLock) + { + _hasErrorOrWarning = false; + + // Merge all error lists, then reset them + AddErrorsToCollection(_errors, ref allErrors, ref broken); + AddErrorsToCollection(_warnings, ref allErrors, ref broken); + _errors = null; + _warnings = null; + + // We also process the pre-attention error lists here since, if we are here and they are populated, then an error occurred while sending attention so we should show the errors now (otherwise they'd be lost) + AddErrorsToCollection(_preAttentionErrors, ref allErrors, ref broken); + AddErrorsToCollection(_preAttentionWarnings, ref allErrors, ref broken); + _preAttentionErrors = null; + _preAttentionWarnings = null; + } + + return allErrors; + } + + private void AddErrorsToCollection(SqlErrorCollection inCollection, ref SqlErrorCollection collectionToAddTo, ref bool broken) + { + if (inCollection != null) + { + foreach (SqlError error in inCollection) + { + collectionToAddTo.Add(error); + broken |= (error.Class >= TdsEnums.FATAL_ERROR_CLASS); + } + } + } + + /// + /// Stores away current errors and warnings so that an attention can be processed + /// + internal void StoreErrorAndWarningForAttention() + { + lock (_errorAndWarningsLock) + { + Debug.Assert(_preAttentionErrors == null && _preAttentionWarnings == null, "Can't store errors for attention because there are already errors stored"); + + _hasErrorOrWarning = false; + + _preAttentionErrors = _errors; + _preAttentionWarnings = _warnings; + + _errors = null; + _warnings = null; + } + } + + /// + /// Restores errors and warnings that were stored in order to process an attention + /// + internal void RestoreErrorAndWarningAfterAttention() + { + lock (_errorAndWarningsLock) + { + Debug.Assert(_errors == null && _warnings == null, "Can't restore errors after attention because there are already other errors"); + + _hasErrorOrWarning = (((_preAttentionErrors != null) && (_preAttentionErrors.Count > 0)) || ((_preAttentionWarnings != null) && (_preAttentionWarnings.Count > 0))); + + _errors = _preAttentionErrors; + _warnings = _preAttentionWarnings; + + _preAttentionErrors = null; + _preAttentionWarnings = null; + } + } + + /// + /// Checks if an error is stored in _error and, if so, throws an error + /// + internal void CheckThrowSNIException() + { + if (HasErrorOrWarning) + { + ThrowExceptionAndWarning(); + } + } + + private static void ReadAsyncCallbackComplete(object state) + { + TaskCompletionSource source = (TaskCompletionSource)state; + source.TrySetResult(null); + } + + ///////////////////////////////////////// + // Network/Packet Writing & Processing // + ///////////////////////////////////////// + + // + // Takes a secure string and offsets and saves them for a write latter when the information is written out to SNI Packet + // This method is provided to better handle the life cycle of the clear text of the secure string + // This method also ensures that the clear text is not held in the unpined managed buffer so that it avoids getting moved around by CLR garbage collector + // TdsParserStaticMethods.EncryptPassword operation is also done in the unmanaged buffer for the clear text later + // + internal void WriteSecureString(SecureString secureString) + { + Debug.Assert(_securePasswords[0] == null || _securePasswords[1] == null, "There are more than two secure passwords"); + + int index = _securePasswords[0] != null ? 1 : 0; + + _securePasswords[index] = secureString; + _securePasswordOffsetsInBuffer[index] = _outBytesUsed; + + // loop through and write the entire array + int lengthInBytes = secureString.Length * 2; + + // It is guaranteed both secure password and secure change password should fit into the first packet + // Given current TDS format and implementation it is not possible that one of secure string is the last item and exactly fill up the output buffer + // if this ever happens and it is correct situation, the packet needs to be written out after _outBytesUsed is update + Debug.Assert((_outBytesUsed + lengthInBytes) < _outBuff.Length, "Passwords cannot be split into two different packet or the last item which fully fill up _outBuff!!!"); + + _outBytesUsed += lengthInBytes; + } + + internal void ResetSecurePasswordsInformation() + { + for (int i = 0; i < _securePasswords.Length; ++i) + { + _securePasswords[i] = null; + _securePasswordOffsetsInBuffer[i] = 0; + } + } + + internal Task WaitForAccumulatedWrites() + { + // Checked for stored exceptions + Exception delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); + if (delayedException != null) + { + throw delayedException; + } +#pragma warning restore 420 + + if (_asyncWriteCount == 0) + { + return null; + } + + _writeCompletionSource = new TaskCompletionSource(); + Task task = _writeCompletionSource.Task; + + // Ensure that _writeCompletionSource is set before checking state + Interlocked.MemoryBarrier(); + + // Now that we have set _writeCompletionSource, check if parser is closed or broken + if ((_parser.State == TdsParserState.Closed) || (_parser.State == TdsParserState.Broken)) + { + throw ADP.ClosedConnectionError(); + } + + // Check for stored exceptions + delayedException = Interlocked.Exchange(ref _delayedWriteAsyncCallbackException, null); + if (delayedException != null) + { + throw delayedException; + } + + // If there are no outstanding writes, see if we can shortcut and return null + if ((_asyncWriteCount == 0) && ((!task.IsCompleted) || (task.Exception == null))) + { + task = null; + } + + return task; + } + + // Takes in a single byte and writes it to the buffer. If the buffer is full, it is flushed + // and then the buffer is re-initialized in flush() and then the byte is put in the buffer. + internal void WriteByte(byte b) + { + Debug.Assert(_outBytesUsed <= _outBuff.Length, "ERROR - TDSParser: _outBytesUsed > _outBuff.Length"); + + // check to make sure we haven't used the full amount of space available in the buffer, if so, flush it + if (_outBytesUsed == _outBuff.Length) + { + WritePacket(TdsEnums.SOFTFLUSH, canAccumulate: true); + } + // set byte in buffer and increment the counter for number of bytes used in the out buffer + _outBuff[_outBytesUsed++] = b; + } + + private void CancelWritePacket() + { + Debug.Assert(_cancelled, "Should not call CancelWritePacket if _cancelled is not set"); + + _parser.Connection.ThreadHasParserLockForClose = true; // In case of error, let the connection know that we are holding the lock + try + { + // Send the attention and wait for the ATTN_ACK + SendAttention(); + ResetCancelAndProcessAttention(); + + // Let the caller know that we've given up + throw SQL.OperationCancelled(); + } + finally + { + _parser.Connection.ThreadHasParserLockForClose = false; + } + } + ///////////////////////////////////////// // Network/Packet Reading & Processing // ///////////////////////////////////////// @@ -1992,8 +2601,6 @@ internal TdsOperationStatus TrySkipBytes(int num) internal TdsOperationStatus TryReadNetworkPacket() { - TdsParser.ReliabilitySection.Assert("unreliable call to TryReadNetworkPacket"); // you need to setup for a thread abort somewhere before you call this method - #if DEBUG Debug.Assert(!_shouldHaveEnoughData || _attentionSent, "Caller said there should be enough data, but we are currently reading a packet"); #endif @@ -2077,8 +2684,6 @@ internal void ReadSniSyncOverAsync() bool shouldDecrement = false; try { - TdsParser.ReliabilitySection.Assert("unreliable call to ReadSniSync"); // you need to setup for a thread abort somewhere before you call this method - Interlocked.Increment(ref _readingCount); shouldDecrement = true; @@ -2093,7 +2698,8 @@ internal void ReadSniSyncOverAsync() } if (TdsEnums.SNI_SUCCESS == error) - { // Success - process results! + { + // Success - process results! Debug.Assert(!IsPacketEmpty(readPacket), "ReadNetworkPacket cannot be null in synchronous operation!"); @@ -2109,8 +2715,8 @@ internal void ReadSniSyncOverAsync() #endif } else - { // Failure! - + { + // Failure! Debug.Assert(!IsValidPacket(readPacket), "unexpected readPacket without corresponding SNIPacketRelease"); ReadSniError(this, error); @@ -2499,8 +3105,6 @@ internal bool IsConnectionAlive(bool throwOnException) } else { - TdsParser.ReliabilitySection.Assert("unreliable call to IsConnectionAlive"); // you need to setup for a thread abort somewhere before you call this method - SniContext = SniContext.Snix_Connect; uint error = CheckConnection(); @@ -2526,44 +3130,191 @@ internal bool IsConnectionAlive(bool throwOnException) return isAlive; } - /* + internal Task WriteByteSpan(ReadOnlySpan span, bool canAccumulate = true, TaskCompletionSource completion = null) + { + return WriteBytes(span, span.Length, 0, canAccumulate, completion); + } + + internal Task WriteByteArray(byte[] b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null) + { + return WriteBytes(ReadOnlySpan.Empty, len, offsetBuffer, canAccumulate, completion, b); + } + + // + // Takes a span or a byte array and writes it to the buffer + // If you pass in a span and a null array then the span wil be used. + // If you pass in a non-null array then the array will be used and the span is ignored. + // if the span cannot be written into the current packet then the remaining contents of the span are copied to a + // new heap allocated array that will used to callback into the method to continue the write operation. + private Task WriteBytes(ReadOnlySpan b, int len, int offsetBuffer, bool canAccumulate = true, TaskCompletionSource completion = null, byte[] array = null) + { + if (array != null) + { + b = new ReadOnlySpan(array, offsetBuffer, len); + } + try + { + bool async = _parser._asyncWrite; // NOTE: We are capturing this now for the assert after the Task is returned, since WritePacket will turn off async if there is an exception + Debug.Assert(async || _asyncWriteCount == 0); + // Do we have to send out in packet size chunks, or can we rely on netlib layer to break it up? + // would prefer to do something like: + // + // if (len > what we have room for || len > out buf) + // flush buffer + // UnsafeNativeMethods.Write(b) + // + + int offset = offsetBuffer; + + Debug.Assert(b.Length >= len, "Invalid length sent to WriteBytes()!"); + + // loop through and write the entire array + do + { + if ((_outBytesUsed + len) > _outBuff.Length) + { + // If the remainder of the data won't fit into the buffer, then we have to put + // whatever we can into the buffer, and flush that so we can then put more into + // the buffer on the next loop of the while. + + int remainder = _outBuff.Length - _outBytesUsed; + + // write the remainder + Span copyTo = _outBuff.AsSpan(_outBytesUsed, remainder); + ReadOnlySpan copyFrom = b.Slice(0, remainder); + + Debug.Assert(copyTo.Length == copyFrom.Length, $"copyTo.Length:{copyTo.Length} and copyFrom.Length{copyFrom.Length:D} should be the same"); + + copyFrom.CopyTo(copyTo); + + offset += remainder; + _outBytesUsed += remainder; + len -= remainder; + b = b.Slice(remainder, len); + + Task packetTask = WritePacket(TdsEnums.SOFTFLUSH, canAccumulate); + + if (packetTask != null) + { + Task task = null; + Debug.Assert(async, "Returned task in sync mode"); + if (completion == null) + { + completion = new TaskCompletionSource(); + task = completion.Task; // we only care about return from topmost call, so do not access Task property in other cases + } + + if (array == null) + { + byte[] tempArray = new byte[len]; + Span copyTempTo = tempArray.AsSpan(); + + Debug.Assert(copyTempTo.Length == b.Length, $"copyTempTo.Length:{copyTempTo.Length} and copyTempFrom.Length:{b.Length:D} should be the same"); + + b.CopyTo(copyTempTo); + array = tempArray; + offset = 0; + } + + WriteBytesSetupContinuation(array, len, completion, offset, packetTask); + return task; + } + } + else + { + //((stateObj._outBytesUsed + len) <= stateObj._outBuff.Length ) + // Else the remainder of the string will fit into the buffer, so copy it into the + // buffer and then break out of the loop. + + Span copyTo = _outBuff.AsSpan(_outBytesUsed, len); + ReadOnlySpan copyFrom = b.Slice(0, len); + + Debug.Assert(copyTo.Length == copyFrom.Length, $"copyTo.Length:{copyTo.Length} and copyFrom.Length:{copyFrom.Length:D} should be the same"); + + copyFrom.CopyTo(copyTo); + + // handle out buffer bytes used counter + _outBytesUsed += len; + break; + } + } while (len > 0); + + if (completion != null) + { + completion.SetResult(null); + } + return null; + } + catch (Exception e) + { + if (completion != null) + { + completion.SetException(e); + return null; + } + else + { + throw; + } + } + } + + // This is in its own method to avoid always allocating the lambda in WriteBytes + private void WriteBytesSetupContinuation(byte[] array, int len, TaskCompletionSource completion, int offset, Task packetTask) + { + AsyncHelper.ContinueTask(packetTask, completion, + onSuccess: () => WriteBytes(ReadOnlySpan.Empty, len: len, offsetBuffer: offset, canAccumulate: false, completion: completion, array) + ); + } - // leave this in. comes handy if you have to do Console.WriteLine style debugging ;) - private void DumpBuffer() { - Console.WriteLine("dumping buffer"); - Console.WriteLine("_inBytesRead = {0}", _inBytesRead); - Console.WriteLine("_inBytesUsed = {0}", _inBytesUsed); + /// + /// Creates a human-readable message containing the _inBytesRead, _inBytesUsed counters + /// and the used and unused portions of the _inBuff array to help diagnosing problems with + /// packet parsing. + /// + /// + internal string DumpBuffer() + { + StringBuilder buffer = new StringBuilder(128); + buffer.AppendLine("dumping buffer"); + buffer.AppendFormat("_inBytesRead = {0}", _inBytesRead).AppendLine(); + buffer.AppendFormat("_inBytesUsed = {0}", _inBytesUsed).AppendLine(); int cc = 0; // character counter int i; - Console.WriteLine("used buffer:"); - for (i=0; i< _inBytesUsed; i++) { + buffer.AppendLine("used buffer:"); + for (i=0; i< _inBytesUsed; i++) + { if (cc==16) { - Console.WriteLine(); + buffer.AppendLine(); cc = 0; } - Console.Write("{0,-2:X2} ", _inBuff[i]); + buffer.AppendFormat("{0,-2:X2} ", _inBuff[i]); cc++; } - if (cc>0) { - Console.WriteLine(); + if (cc>0) + { + buffer.AppendLine(); } cc = 0; - Console.WriteLine("unused buffer:"); - for (i=_inBytesUsed; i<_inBytesRead; i++) { - if (cc==16) { - Console.WriteLine(); + buffer.AppendLine("unused buffer:"); + for (i=_inBytesUsed; i<_inBytesRead; i++) + { + if (cc==16) + { + buffer.AppendLine(); cc = 0; } - Console.Write("{0,-2:X2} ", _inBuff[i]); + buffer.AppendFormat("{0,-2:X2} ", _inBuff[i]); cc++; } - if (cc>0) { - Console.WriteLine(); + if (cc>0) + { + buffer.AppendLine(); } + return buffer.ToString(); } - */ - + internal void SetSnapshot() { StateSnapshot snapshot = _snapshot; @@ -2592,6 +3343,111 @@ internal void ResetSnapshot() _snapshotReplay = false; } + + /// + /// Debug Only: Ensures that the TdsParserStateObject has no lingering state and can safely be re-used + /// + [Conditional("DEBUG")] + internal void AssertStateIsClean() + { + // If our TdsParser is closed or broken, then we don't really care about our state + TdsParser parser = _parser; + if ((parser != null) && (parser.State != TdsParserState.Closed) && (parser.State != TdsParserState.Broken)) + { + // Async reads + Debug.Assert(_snapshot == null && !_snapshotReplay, "StateObj has leftover snapshot state"); + Debug.Assert(!_asyncReadWithoutSnapshot, "StateObj has AsyncReadWithoutSnapshot still enabled"); + Debug.Assert(_executionContext == null, "StateObj has a stored execution context from an async read"); + // Async writes + Debug.Assert(_asyncWriteCount == 0, "StateObj still has outstanding async writes"); + Debug.Assert(_delayedWriteAsyncCallbackException == null, "StateObj has an unobserved exceptions from an async write"); + // Attention\Cancellation\Timeouts + Debug.Assert(!HasReceivedAttention && !_attentionSent && !_attentionSending, $"StateObj is still dealing with attention: Sent: {_attentionSent}, Received: {HasReceivedAttention}, Sending: {_attentionSending}"); + Debug.Assert(!_cancelled, "StateObj still has cancellation set"); + Debug.Assert(_timeoutState == TimeoutState.Stopped, "StateObj still has internal timeout set"); + // Errors and Warnings + Debug.Assert(!_hasErrorOrWarning, "StateObj still has stored errors or warnings"); + } + } + +#if DEBUG + internal void CompletePendingReadWithSuccess(bool resetForcePendingReadsToWait) + { + TaskCompletionSource realNetworkPacketTaskSource = _realNetworkPacketTaskSource; + TaskCompletionSource networkPacketTaskSource = _networkPacketTaskSource; + + Debug.Assert(s_forcePendingReadsToWaitForUser, "Not forcing pends to wait for user - can't force complete"); + Debug.Assert(networkPacketTaskSource != null, "No pending read to complete"); + + try + { + if (realNetworkPacketTaskSource != null) + { + // Wait for the real read to complete + realNetworkPacketTaskSource.Task.Wait(); + } + } + finally + { + if (networkPacketTaskSource != null) + { + if (resetForcePendingReadsToWait) + { + s_forcePendingReadsToWaitForUser = false; + } + + networkPacketTaskSource.TrySetResult(null); + } + } + } + + internal void CompletePendingReadWithFailure(int errorCode, bool resetForcePendingReadsToWait) + { + TaskCompletionSource realNetworkPacketTaskSource = _realNetworkPacketTaskSource; + TaskCompletionSource networkPacketTaskSource = _networkPacketTaskSource; + + Debug.Assert(s_forcePendingReadsToWaitForUser, "Not forcing pends to wait for user - can't force complete"); + Debug.Assert(networkPacketTaskSource != null, "No pending read to complete"); + + try + { + if (realNetworkPacketTaskSource != null) + { + // Wait for the real read to complete + realNetworkPacketTaskSource.Task.Wait(); + } + } + finally + { + if (networkPacketTaskSource != null) + { + if (resetForcePendingReadsToWait) + { + s_forcePendingReadsToWaitForUser = false; + } + + AddError(new SqlError(errorCode, 0x00, TdsEnums.FATAL_ERROR_CLASS, _parser.Server, string.Empty, string.Empty, 0)); + try + { + ThrowExceptionAndWarning(); + } + catch (Exception ex) + { + networkPacketTaskSource.TrySetException(ex); + } + } + } + } +#endif + + internal void CloneCleanupAltMetaDataSetArray() + { + if (_snapshot != null) + { + _snapshot.CloneCleanupAltMetaDataSetArray(); + } + } + sealed partial class StateSnapshot { private sealed partial class PacketData diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Managed.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Unix.cs similarity index 73% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Managed.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Unix.cs index 86bc3013a9..f7cb55d451 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Managed.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Unix.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using Microsoft.Data.SqlClient.SNI; +using Microsoft.Data.SqlClient.ManagedSni; namespace Microsoft.Data.SqlClient { @@ -13,14 +13,14 @@ internal sealed class TdsParserStateObjectFactory public static readonly TdsParserStateObjectFactory Singleton = new TdsParserStateObjectFactory(); - public EncryptionOptions EncryptionOptions => SNI.SNILoadHandle.SingletonInstance.Options; + public EncryptionOptions EncryptionOptions => ManagedSni.SniLoadHandle.SingletonInstance.Options; - public uint SNIStatus => SNI.SNILoadHandle.SingletonInstance.Status; + public uint SNIStatus => ManagedSni.SniLoadHandle.SingletonInstance.Status; /// /// Verify client encryption possibility. /// - public bool ClientOSEncryptionSupport => SNI.SNILoadHandle.SingletonInstance.ClientOSEncryptionSupport; + public bool ClientOSEncryptionSupport => ManagedSni.SniLoadHandle.SingletonInstance.ClientOSEncryptionSupport; public TdsParserStateObject CreateTdsParserStateObject(TdsParser parser) { diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Windows.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Windows.cs similarity index 65% rename from src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Windows.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Windows.cs index 6785d7c2fd..8b8fe9186b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Windows.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStateObjectFactory.Windows.cs @@ -2,8 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#if NET using System; -using Microsoft.Data.SqlClient.SNI; +using Microsoft.Data.SqlClient.ManagedSni; +#endif namespace Microsoft.Data.SqlClient { @@ -13,23 +15,43 @@ internal sealed class TdsParserStateObjectFactory private const string UseManagedNetworkingOnWindows = "Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows"; - private static bool shouldUseManagedSNI; +#if NET + private static bool s_shouldUseManagedSNI; // If the appcontext switch is set then Use Managed SNI based on the value. Otherwise Native SNI.dll will be used by default. - public static bool UseManagedSNI => - AppContext.TryGetSwitch(UseManagedNetworkingOnWindows, out shouldUseManagedSNI) ? shouldUseManagedSNI : false; + public static bool UseManagedSNI => + AppContext.TryGetSwitch(UseManagedNetworkingOnWindows, out s_shouldUseManagedSNI) ? s_shouldUseManagedSNI : false; +#else + public const bool UseManagedSNI = false; +#endif - public EncryptionOptions EncryptionOptions => UseManagedSNI ? SNI.SNILoadHandle.SingletonInstance.Options : SNILoadHandle.SingletonInstance.Options; + public EncryptionOptions EncryptionOptions => +#if NET + UseManagedSNI ? ManagedSni.SniLoadHandle.SingletonInstance.Options : SNILoadHandle.SingletonInstance.Options; +#else + SNILoadHandle.SingletonInstance.Options; +#endif - public uint SNIStatus => UseManagedSNI ? SNI.SNILoadHandle.SingletonInstance.Status : SNILoadHandle.SingletonInstance.Status; + public uint SNIStatus => +#if NET + UseManagedSNI ? ManagedSni.SniLoadHandle.SingletonInstance.Status : SNILoadHandle.SingletonInstance.Status; +#else + SNILoadHandle.SingletonInstance.Status; +#endif /// /// Verify client encryption possibility. /// - public bool ClientOSEncryptionSupport => UseManagedSNI ? SNI.SNILoadHandle.SingletonInstance.ClientOSEncryptionSupport : SNILoadHandle.SingletonInstance.ClientOSEncryptionSupport; + public bool ClientOSEncryptionSupport => +#if NET + UseManagedSNI ? ManagedSni.SniLoadHandle.SingletonInstance.ClientOSEncryptionSupport : SNILoadHandle.SingletonInstance.ClientOSEncryptionSupport; +#else + SNILoadHandle.SingletonInstance.ClientOSEncryptionSupport; +#endif public TdsParserStateObject CreateTdsParserStateObject(TdsParser parser) { +#if NET if (UseManagedSNI) { SqlClientEventSource.Log.TryTraceEvent("TdsParserStateObjectFactory.CreateTdsParserStateObject | Info | Found AppContext switch '{0}' enabled, managed networking implementation will be used." @@ -42,15 +64,20 @@ public TdsParserStateObject CreateTdsParserStateObject(TdsParser parser) , UseManagedNetworkingOnWindows); return new TdsParserStateObjectNative(parser); } +#else + return new TdsParserStateObjectNative(parser); +#endif } internal TdsParserStateObject CreateSessionObject(TdsParser tdsParser, TdsParserStateObject _pMarsPhysicalConObj, bool v) { +#if NET if (TdsParserStateObjectFactory.UseManagedSNI) { return new TdsParserStateObjectManaged(tdsParser, _pMarsPhysicalConObj, true); } else +#endif { return new TdsParserStateObjectNative(tdsParser, _pMarsPhysicalConObj, true); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs index 3954ad6ed8..e21bfc7826 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserStaticMethods.cs @@ -23,12 +23,12 @@ private TdsParserStaticMethods() { /* prevent utility class from being insantiat [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] static internal void AliasRegistryLookup(ref string host, ref string protocol) { - if (!ADP.IsEmpty(host)) + if (!string.IsNullOrEmpty(host)) { const String folder = "SOFTWARE\\Microsoft\\MSSQLServer\\Client\\ConnectTo"; // Put a try...catch... around this so we don't abort ANY connection if we can't read the registry. string aliasLookup = (string)ADP.LocalMachineRegistryValue(folder, host); - if (!ADP.IsEmpty(aliasLookup)) + if (!string.IsNullOrEmpty(aliasLookup)) { /* Result will be in the form of: "DBNMPNTW,\\server\pipe\sql\query". or Result will be in the form of: "DBNETLIB, via:\\server\pipe\sql\query". @@ -139,10 +139,14 @@ internal static int GetCurrentProcessIdForTdsLoginOnly() // Pick up the process Id from the current process instead of randomly generating it. // This would be helpful while tracing application related issues. int processId; +#if NET + processId = Environment.ProcessId; +#else using (System.Diagnostics.Process p = System.Diagnostics.Process.GetCurrentProcess()) { processId = p.Id; } +#endif System.Threading.Volatile.Write(ref s_currentProcessId, processId); } return s_currentProcessId; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsRecordBufferSetter.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsRecordBufferSetter.cs index 90a83057b0..496295e0b8 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsRecordBufferSetter.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsRecordBufferSetter.cs @@ -46,21 +46,9 @@ internal TdsRecordBufferSetter(TdsParserStateObject stateObj, SmiMetaData md) } // TdsRecordBufferSetter supports Setting only - internal override bool CanGet - { - get - { - return false; - } - } + protected override bool CanGet => false; - internal override bool CanSet - { - get - { - return true; - } - } + protected override bool CanSet => true; #endregion @@ -68,21 +56,21 @@ internal override bool CanSet // Set value to null // valid for all types - public override void SetDBNull(SmiEventSink sink, int ordinal) + public override void SetDBNull(int ordinal) { CheckSettingColumn(ordinal); _fieldSetters[ordinal].SetDBNull(); } // valid for SqlDbType.Bit - public override void SetBoolean(SmiEventSink sink, int ordinal, bool value) + public override void SetBoolean(int ordinal, bool value) { CheckSettingColumn(ordinal); _fieldSetters[ordinal].SetBoolean(value); } // valid for SqlDbType.TinyInt - public override void SetByte(SmiEventSink sink, int ordinal, byte value) + public override void SetByte(int ordinal, byte value) { CheckSettingColumn(ordinal); _fieldSetters[ordinal].SetByte(value); @@ -92,12 +80,12 @@ public override void SetByte(SmiEventSink sink, int ordinal, byte value) // Use in combination with SetLength to ensure overwriting when necessary // valid for SqlDbTypes: Binary, VarBinary, Image, Udt, Xml // (VarBinary assumed for variants) - public override int SetBytes(SmiEventSink sink, int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) + public override int SetBytes(int ordinal, long fieldOffset, byte[] buffer, int bufferOffset, int length) { CheckWritingToColumn(ordinal); return _fieldSetters[ordinal].SetBytes(fieldOffset, buffer, bufferOffset, length); } - public override void SetBytesLength(SmiEventSink sink, int ordinal, long length) + public override void SetBytesLength(int ordinal, long length) { CheckSettingColumn(ordinal); _fieldSetters[ordinal].SetBytesLength(length); @@ -107,130 +95,133 @@ public override void SetBytesLength(SmiEventSink sink, int ordinal, long length) // Use in combination with SetLength to ensure overwriting when necessary // valid for character types: Char, VarChar, Text, NChar, NVarChar, NText // (NVarChar and global clr collation assumed for variants) - public override int SetChars(SmiEventSink sink, int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) + public override int SetChars(int ordinal, long fieldOffset, char[] buffer, int bufferOffset, int length) { CheckWritingToColumn(ordinal); return _fieldSetters[ordinal].SetChars(fieldOffset, buffer, bufferOffset, length); } - public override void SetCharsLength(SmiEventSink sink, int ordinal, long length) + public override void SetCharsLength(int ordinal, long length) { CheckSettingColumn(ordinal); _fieldSetters[ordinal].SetCharsLength(length); } // valid for character types: Char, VarChar, Text, NChar, NVarChar, NText - public override void SetString(SmiEventSink sink, int ordinal, string value, int offset, int length) + public override void SetString(int ordinal, string value, int offset, int length) { CheckSettingColumn(ordinal); _fieldSetters[ordinal].SetString(value, offset, length); } // valid for SqlDbType.SmallInt - public override void SetInt16(SmiEventSink sink, int ordinal, short value) + public override void SetInt16(int ordinal, short value) { CheckSettingColumn(ordinal); _fieldSetters[ordinal].SetInt16(value); } // valid for SqlDbType.Int - public override void SetInt32(SmiEventSink sink, int ordinal, int value) + public override void SetInt32(int ordinal, int value) { CheckSettingColumn(ordinal); _fieldSetters[ordinal].SetInt32(value); } // valid for SqlDbType.BigInt, SqlDbType.Money, SqlDbType.SmallMoney - public override void SetInt64(SmiEventSink sink, int ordinal, long value) + public override void SetInt64(int ordinal, long value) { CheckSettingColumn(ordinal); _fieldSetters[ordinal].SetInt64(value); } // valid for SqlDbType.Real - public override void SetSingle(SmiEventSink sink, int ordinal, float value) + public override void SetSingle(int ordinal, float value) { CheckSettingColumn(ordinal); _fieldSetters[ordinal].SetSingle(value); } // valid for SqlDbType.Float - public override void SetDouble(SmiEventSink sink, int ordinal, double value) + public override void SetDouble(int ordinal, double value) { CheckSettingColumn(ordinal); _fieldSetters[ordinal].SetDouble(value); } // valid for SqlDbType.Numeric (uses SqlDecimal since Decimal cannot hold full range) - public override void SetSqlDecimal(SmiEventSink sink, int ordinal, SqlDecimal value) + public override void SetSqlDecimal(int ordinal, SqlDecimal value) { CheckSettingColumn(ordinal); _fieldSetters[ordinal].SetSqlDecimal(value); } // valid for DateTime, SmallDateTime, Date, DateTime2 - public override void SetDateTime(SmiEventSink sink, int ordinal, DateTime value) + public override void SetDateTime(int ordinal, DateTime value) { CheckSettingColumn(ordinal); _fieldSetters[ordinal].SetDateTime(value); } // valid for UniqueIdentifier - public override void SetGuid(SmiEventSink sink, int ordinal, Guid value) + public override void SetGuid(int ordinal, Guid value) { CheckSettingColumn(ordinal); _fieldSetters[ordinal].SetGuid(value); } // valid for SqlDbType.Time - public override void SetTimeSpan(SmiEventSink sink, int ordinal, TimeSpan value) + public override void SetTimeSpan(int ordinal, TimeSpan value) { CheckSettingColumn(ordinal); _fieldSetters[ordinal].SetTimeSpan(value); } // valid for DateTimeOffset - public override void SetDateTimeOffset(SmiEventSink sink, int ordinal, DateTimeOffset value) + public override void SetDateTimeOffset(int ordinal, DateTimeOffset value) { CheckSettingColumn(ordinal); _fieldSetters[ordinal].SetDateTimeOffset(value); } // valid for SqlDbType.Variant - public override void SetVariantMetaData(SmiEventSink sink, int ordinal, SmiMetaData metaData) + public override void SetVariantMetaData(int ordinal, SmiMetaData metaData) { CheckWritingToColumn(ordinal); _fieldSetters[ordinal].SetVariantType(metaData); } // valid for multi-valued types - internal override void NewElement(SmiEventSink sink) + internal override void NewElement() { -#if DEBUG + #if DEBUG SkipPossibleDefaultedColumns(ReadyForToken); Debug.Assert(ReadyForToken == _currentField, "Not on first or last column!"); -#endif + #endif // For TVP types, write new-row token Debug.Assert(_metaData.IsMultiValued, "Unsupported call for single-valued types"); _stateObj.WriteByte(TdsEnums.TVP_ROW_TOKEN); -#if DEBUG + + #if DEBUG _currentField = 0; -#endif + #endif } - internal override void EndElements(SmiEventSink sink) + internal override void EndElements() { -#if DEBUG + #if DEBUG SkipPossibleDefaultedColumns(ReadyForToken); Debug.Assert(ReadyForToken == _currentField, "Not on first or last column!"); Debug.Assert(_metaData.IsMultiValued, "Unsupported call for single-valued types"); -#endif + #endif + // For TVP types, write no-more-rows token _stateObj.WriteByte(TdsEnums.TVP_END_TOKEN); -#if DEBUG + + #if DEBUG _currentField = EndElementsCalled; -#endif + #endif } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/BufferWriterExtensions.netfx.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/BufferWriterExtensions.netfx.cs new file mode 100644 index 0000000000..22cbb7cb52 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/BufferWriterExtensions.netfx.cs @@ -0,0 +1,34 @@ +// 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. + +#if NETFRAMEWORK + +using System; +using System.Buffers; +using System.Text; + +namespace Microsoft.Data.SqlClient.Utilities +{ + internal static class BufferWriterExtensions + { + internal static long GetBytes(this Encoding encoding, string str, IBufferWriter bufferWriter) + { + var count = encoding.GetByteCount(str); + var array = ArrayPool.Shared.Rent(count); + + try + { + var length = encoding.GetBytes(str, 0, str.Length, array, 0); + bufferWriter.Write(array.AsSpan(0, length)); + return length; + } + finally + { + ArrayPool.Shared.Return(array); + } + } + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlObjectPool.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/ObjectPool.cs similarity index 75% rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlObjectPool.cs rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/ObjectPool.cs index 8dcb292e04..a3b8c11cff 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlObjectPool.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/ObjectPool.cs @@ -2,22 +2,38 @@ // 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.Threading; -namespace Microsoft.Data.SqlClient +namespace Microsoft.Data.SqlClient.Utilities { // this is a very simple threadsafe pool derived from the aspnet/extensions default pool implementation // https://github.com/dotnet/extensions/blob/release/3.1/src/ObjectPool/src/DefaultObjectPool.cs - internal sealed class SqlObjectPool where T : class + internal sealed class ObjectPool where T : class { private readonly ObjectWrapper[] _items; + private readonly Action _onReturned; + private readonly Func _onCreate; + private T _firstItem; - public SqlObjectPool(int maximumRetained) + public ObjectPool(int maximumRetained, Func onCreate = null, Action onReturned = null) { // -1 due to _firstItem _items = new ObjectWrapper[maximumRetained - 1]; + _onReturned = onReturned; + _onCreate = onCreate; + } + + public T Rent() + { + if (TryGet(out var item)) + { + return item; + } + + return _onCreate?.Invoke() ?? throw new InvalidOperationException("Can only rent from a pool if an onCreate delegate is available"); } public bool TryGet(out T item) @@ -48,6 +64,8 @@ public bool TryGet(out T item) public void Return(T item) { + _onReturned?.Invoke(item); + if (_firstItem != null || Interlocked.CompareExchange(ref _firstItem, item, null) != null) { var items = _items; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/ObjectPools.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/ObjectPools.cs new file mode 100644 index 0000000000..ab046f63bf --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Utilities/ObjectPools.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Buffers; + +namespace Microsoft.Data.SqlClient.Utilities +{ + /// + /// This is a collection of general object pools that can be reused as needed. + /// + internal static class ObjectPools + { + private static readonly Lazy>> s_bufferWriterPool = + new(() => new ObjectPool>( + maximumRetained: 20, + onCreate: () => new ArrayBufferWriter(), + onReturned: bufferWriter => bufferWriter.Clear())); + + internal static ObjectPool> BufferWriter => s_bufferWriterPool.Value; + } + +} diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProvider.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProvider.cs index 318f2194fc..7966d53f77 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProvider.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProvider.cs @@ -7,8 +7,8 @@ using System.Diagnostics; using System.IO; using System.Net.Http; -using System.Runtime.Serialization.Json; using System.Security.Cryptography.X509Certificates; +using System.Text.Json; using System.Threading; namespace Microsoft.Data.SqlClient @@ -72,8 +72,7 @@ protected override byte[] MakeRequest(string url) using (Stream stream = s_client.GetStreamAsync(url).ConfigureAwait(false).GetAwaiter().GetResult()) { - var deserializer = new DataContractJsonSerializer(typeof(byte[])); - return (byte[])deserializer.ReadObject(stream); + return JsonSerializer.Deserialize>(stream)?.ToArray(); } } catch (Exception e) @@ -168,7 +167,11 @@ internal class HealthReport public HealthReport(byte[] payload) { Size = payload.Length; +#if NET9_0_OR_GREATER Certificate = X509CertificateLoader.LoadCertificate(payload); +#else + Certificate = new X509Certificate2(payload); +#endif } public int GetSizeInPayload() @@ -455,7 +458,7 @@ internal enum EnclaveIdentityFlags ENCLAVE_FLAG_DYNAMIC_DEBUG_ACTIVE = 0x00000004 } - #endregion +#endregion internal static class EnclaveHelpers { diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs index 76089a501e..198ac9f10f 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/VirtualSecureModeEnclaveProviderBase.cs @@ -202,7 +202,7 @@ private X509Certificate2Collection GetSigningCertificate(string attestationUrl, try { - var s = new SignedCms(); + SignedCms s = new SignedCms(); s.Decode(data); certificateCollection.AddRange(s.Certificates); } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlDbTypeExtensions.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlDbTypeExtensions.cs index df7f597517..96244fb7a8 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlDbTypeExtensions.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlDbTypeExtensions.cs @@ -14,6 +14,12 @@ public static class SqlDbTypeExtensions public const SqlDbType Json = SqlDbType.Json; #else public const SqlDbType Json = (SqlDbType)35; +#endif + /// +#if NET10_0_OR_GREATER + public const SqlDbType Vector = SqlDbType.Vector; +#else + public const SqlDbType Vector = (SqlDbType)36; #endif } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.netcore.Unix.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.netcore.Unix.cs new file mode 100644 index 0000000000..9aac2d9d5e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlFileStream.netcore.Unix.cs @@ -0,0 +1,92 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if NET + +using System; +using System.IO; + +namespace Microsoft.Data.SqlTypes +{ + /// + public sealed class SqlFileStream : Stream + { + /// + public SqlFileStream(string path, byte[] transactionContext, FileAccess access) + { + throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); + } + + /// + public SqlFileStream(string path, byte[] transactionContext, FileAccess access, FileOptions options, Int64 allocationSize) + { + throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); + } + + /// + public string Name + { + get => throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); + } + + /// + public byte[] TransactionContext + { + get => throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); + } + + /// + public override bool CanRead + { + get => throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); + } + + /// + public override bool CanSeek + { + get => throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); + } + + /// + public override bool CanWrite + { + get => throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); + } + + /// + public override long Length + { + get => throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); + } + + /// + public override long Position + { + get => throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); + set => throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); + } + + /// + public override void Flush() => + throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); + + /// + public override int Read(byte[] buffer, int offset, int count) => + throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); + + /// + public override long Seek(long offset, System.IO.SeekOrigin origin) => + throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); + + /// + public override void SetLength(long value) => + throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); + + /// + public override void Write(byte[] buffer, int offset, int count) => + throw new PlatformNotSupportedException(Strings.SqlFileStream_NotSupported); + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlJson.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlJson.cs index 50841d3a68..8d0ee74cc6 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlJson.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlJson.cs @@ -2,45 +2,47 @@ // 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.Data.SqlTypes; -using System.Text; +#if NET +using System.Diagnostics.CodeAnalysis; +#endif using System.Text.Json; #nullable enable namespace Microsoft.Data.SqlTypes { - /// + /// public class SqlJson : INullable { - - /// - /// True if null. - /// - private bool _isNull; - - private readonly string? _jsonString; + // Our serialized JSON string, or null. + private readonly string? _jsonString = null; /// public SqlJson() { - SetNull(); } /// - public SqlJson(string? jsonString) + #if NET + public SqlJson([StringSyntax(StringSyntaxAttribute.Json)] string? jsonString) + #else + public SqlJson(string? jsonString) + #endif { if (jsonString == null) { - SetNull(); - } - else - { - // TODO: We need to validate the Json before storing it. - ValidateJson(jsonString); - _jsonString = jsonString; + return; } + + // Ask JsonDocument to parse it for validity, or throw. + // + // Note that we do not support trailing commas or comments in the + // JSON. + // + JsonDocument.Parse(jsonString).Dispose(); + + _jsonString = jsonString; } /// @@ -48,16 +50,15 @@ public SqlJson(JsonDocument? jsonDoc) { if (jsonDoc == null) { - SetNull(); - } - else - { - _jsonString = jsonDoc.RootElement.GetRawText(); + return; } + + // Save the serialized JSON string from the document, or throw. + _jsonString = jsonDoc.RootElement.GetRawText(); } /// - public bool IsNull => _isNull; + public bool IsNull => _jsonString is null; /// public static SqlJson Null => new(); @@ -71,33 +72,15 @@ public string Value { throw new SqlNullValueException(); } - else - { - return _jsonString!; - } - } - } - private void SetNull() - { - _isNull = true; + return _jsonString!; + } } - private static void ValidateJson(string jsonString) + /// + public override string? ToString() { - // Convert the JSON string to a UTF-8 byte array - byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonString); - - // Create a Utf8JsonReader instance - var reader = new Utf8JsonReader(jsonBytes, isFinalBlock: true, state: default); - - // Read through the JSON data - while (reader.Read()) - { - // The Read method advances the reader to the next token - // If the JSON is invalid, an exception will be thrown - } - // If we reach here, the JSON is valid + return _jsonString; } } } diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs new file mode 100644 index 0000000000..cf04ff8636 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlTypes/SqlVector.cs @@ -0,0 +1,242 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Buffers.Binary; +using System.Data.SqlTypes; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text.Json; +using Microsoft.Data.Common; +using Microsoft.Data.SqlClient; + +#nullable enable + +namespace Microsoft.Data.SqlTypes; + +/// +public readonly struct SqlVector : INullable, ISqlVector +where T : unmanaged +{ + #region Constants + + private const byte VecHeaderMagicNo = 0xA9; + private const byte VecVersionNo = 0x01; + + #endregion + + #region Fields + + private readonly byte _elementType; + private readonly byte _elementSize; + private readonly byte[] _tdsBytes; + private readonly int _size; + + #endregion + + #region Constructors + + private SqlVector(int length) + { + if (length < 0) + { + throw ADP.ArgumentOutOfRange(nameof(length), SQLResource.InvalidArraySizeMessage); + } + + (_elementType, _elementSize) = GetTypeFieldsOrThrow(); + + IsNull = true; + + Length = length; + _size = TdsEnums.VECTOR_HEADER_SIZE + (_elementSize * Length); + + _tdsBytes = Array.Empty(); + Memory = new(); + } + + /// + public static SqlVector CreateNull(int length) => new(length); + + /// + public SqlVector(ReadOnlyMemory memory) + { + (_elementType, _elementSize) = GetTypeFieldsOrThrow(); + + IsNull = false; + + Length = memory.Length; + _size = TdsEnums.VECTOR_HEADER_SIZE + (_elementSize * Length); + + _tdsBytes = MakeTdsBytes(memory); + Memory = memory; + } + + internal SqlVector(byte[] tdsBytes) + { + (_elementType, _elementSize) = GetTypeFieldsOrThrow(); + + (Length, _size) = GetCountsOrThrow(tdsBytes); + + IsNull = false; + + _tdsBytes = tdsBytes; + Memory = new(MakeArray()); + } + + #endregion + + #region Methods + + internal string GetString() + { + if (IsNull) + { + return SQLResource.NullString; + } + return JsonSerializer.Serialize(Memory); + } + + #endregion + + #region Properties + + /// + public bool IsNull { get; } + + /// + public static SqlVector? Null => null; + + /// + public int Length { get; } + + /// + public ReadOnlyMemory Memory { get; } + + #endregion + + #region ISqlVector Internal Properties + byte ISqlVector.ElementType => _elementType; + byte ISqlVector.ElementSize => _elementSize; + byte[] ISqlVector.VectorPayload => _tdsBytes; + int ISqlVector.Size => _size; + + #endregion + + #region Helpers + + private (byte, byte) GetTypeFieldsOrThrow() + { + byte elementType; + byte elementSize; + + if (typeof(T) == typeof(float)) + { + elementType = (byte)MetaType.SqlVectorElementType.Float32; + elementSize = sizeof(float); + } + else + { + throw SQL.VectorTypeNotSupported(typeof(T).FullName); + } + + return (elementType, elementSize); + } + + private byte[] MakeTdsBytes(ReadOnlyMemory values) + { + //Refer to TDS section 2.2.5.5.7 for vector header format + // +------------------------+-----------------+----------------------+------------------+----------------------------+--------------+ + // | Field | Size (bytes) | Example Value | Description | + // +------------------------+-----------------+----------------------+--------------------------------------------------------------+ + // | Layout Format | 1 | 0xA9 | Magic number indicating vector layout format | + // | Layout Version | 1 | 0x01 | Version of the vector format | + // | Number of Dimensions | 2 | NN | Number of vector elements | + // | Dimension Type | 1 | 0x00 | Element type indicator (e.g. 0x00 for float32) | + // | Reserved | 3 | 0x00 0x00 0x00 | Reserved for future use | + // | Stream of Values | NN * sizeof(T) | [element bytes...] | Raw bytes for vector elements | + // +------------------------+-----------------+----------------------+--------------------------------------------------------------+ + + byte[] result = new byte[_size]; + + // Header Bytes + result[0] = VecHeaderMagicNo; + result[1] = VecVersionNo; + result[2] = (byte)(Length & 0xFF); + result[3] = (byte)((Length >> 8) & 0xFF); + result[4] = _elementType; + result[5] = 0x00; + result[6] = 0x00; + result[7] = 0x00; + +#if NETFRAMEWORK + // Copy data via marshaling. + if (MemoryMarshal.TryGetArray(values, out ArraySegment segment)) + { + Buffer.BlockCopy(segment.Array, segment.Offset * _elementSize, result, TdsEnums.VECTOR_HEADER_SIZE, segment.Count * _elementSize); + } + else + { + Buffer.BlockCopy(values.ToArray(), 0, result, TdsEnums.VECTOR_HEADER_SIZE, values.Length * _elementSize); + } +#else + // Fast span-based copy. + var byteSpan = MemoryMarshal.AsBytes(values.Span); + byteSpan.CopyTo(result.AsSpan(TdsEnums.VECTOR_HEADER_SIZE)); +#endif + return result; + } + + private (int, int) GetCountsOrThrow(byte[] rawBytes) + { + // Validate some of the header fields. + if ( + // Do we have enough bytes for the header? + rawBytes.Length < TdsEnums.VECTOR_HEADER_SIZE || + // Do we have the expected magic number? + rawBytes[0] != VecHeaderMagicNo || + // Do we support the version? + rawBytes[1] != VecVersionNo || + // Do the vector types match? + rawBytes[4] != _elementType) + { + // No, so throw. + throw ADP.InvalidVectorHeader(); + } + + // The vector length is an unsigned 16-bit integer, little-endian. + int length = BinaryPrimitives.ReadUInt16LittleEndian(rawBytes.AsSpan(2)); + + // The vector size is the number of bytes required to represent the vector in TDS. + int size = TdsEnums.VECTOR_HEADER_SIZE + (_elementSize * length); + + // Are there exactly enough bytes for the vector elements? + if (rawBytes.Length != size) + { + // No, so throw. + throw ADP.InvalidVectorHeader(); + } + + return (length, size); + } + + private T[] MakeArray() + { + if (_tdsBytes.Length == 0) + { + return Array.Empty(); + } + +#if NETFRAMEWORK + // Allocate array and copy bytes into it + T[] result = new T[Length]; + Buffer.BlockCopy(_tdsBytes, 8, result, 0, _elementSize * Length); + return result; +#else + ReadOnlySpan dataSpan = _tdsBytes.AsSpan(8, _elementSize * Length); + return MemoryMarshal.Cast(dataSpan).ToArray(); +#endif + } + + #endregion +} diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml b/src/Microsoft.Data.SqlClient/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml new file mode 100644 index 0000000000..15090edf8e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/Resources/Microsoft.Data.SqlClient.SqlMetaData.xml @@ -0,0 +1,2453 @@ + + + + + MetaDataCollections + 0 + 0 + DataTable + MetaDataCollections + + + DataSourceInformation + 0 + 0 + DataTable + DataSourceInformation + + + DataTypes + 0 + 0 + PrepareCollection + + + Restrictions + 0 + 0 + DataTable + Restrictions + + + ReservedWords + 0 + 0 + DataTable + ReservedWords + + + Users + 1 + 1 + SQLCommand + select uid, name as user_name, createdate, updatedate from sysusers where (name = @Name or (@Name is null)) + + + Databases + 1 + 1 + SQLCommand + select name as database_name, dbid, crdate as create_date from master..sysdatabases where (name = @Name or (@Name is null)) + 09.99.999.9 + + + Databases + 1 + 1 + SQLCommand + IF OBJECT_ID('master..sysdatabases') IS NULL EXEC sp_executesql N'select name as database_name, dbid, crdate as create_date from sysdatabases where (name = @Name or (@Name is null))',N'@Name NVARCHAR(128)',@Name=@Name ELSE EXEC sp_executesql N'select name as database_name, dbid, crdate as create_date from master..sysdatabases where (name = @Name or (@Name is null))',N'@Name NVARCHAR(128)',@Name=@Name + 10.00.000.0 + + + Tables + 4 + 3 + SQLCommand + select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE from INFORMATION_SCHEMA.TABLES where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Name or (@Name is null)) and (TABLE_TYPE = @TableType or (@TableType is null)) + + + Columns + 4 + 4 + SQLCommand + select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, CHARACTER_SET_CATALOG, CHARACTER_SET_SCHEMA, CHARACTER_SET_NAME, COLLATION_CATALOG from INFORMATION_SCHEMA.COLUMNS where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) and (COLUMN_NAME = @Column or (@Column is null)) order by TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME + 09.99.999.9 + + + Columns + 4 + 4 + SQLCommand + EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, @Column, 0 + 10.00.000.0 + + + AllColumns + 4 + 4 + SQLCommand + EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, @Column, 1 + 10.00.000.0 + + + ColumnSetColumns + 3 + 3 + SQLCommand + EXEC sys.sp_columns_managed @Catalog, @Owner, @Table, null, 2 + 10.00.000.0 + + + StructuredTypeMembers + 4 + 4 + SQLCommand + SELECT DB_NAME() AS TYPE_CATALOG, sc.name AS TYPE_SCHEMA, tt.name AS TYPE_NAME, c.name AS MEMBER_NAME, ColumnProperty(c.object_id, c.name, 'ordinal') AS ORDINAL_POSITION, convert(nvarchar(4000), object_definition(c.default_object_id)) AS MEMBER_DEFAULT, convert(varchar(3), CASE c.is_nullable WHEN 1 THEN 'YES' ELSE 'NO' END) AS IS_NULLABLE, type_name(c.system_type_id) AS DATA_TYPE, ColumnProperty(c.object_id, c.name, 'charmaxlen') AS CHARACTER_MAXIMUM_LENGTH, ColumnProperty(c.object_id, c.name, 'octetmaxlen') AS CHARACTER_OCTET_LENGTH, convert(tinyint, CASE WHEN c.system_type_id IN /* int/decimal/numeric/real/float/money */ (48, 52, 56, 59, 60, 62, 106, 108, 122, 127) THEN c.precision END) AS NUMERIC_PRECISION, convert(smallint, CASE WHEN c.system_type_id IN /* int/money/decimal/numeric */ (48, 52, 56, 60, 106, 108, 122, 127) THEN 10 WHEN c.system_type_id IN /* real/float */ (59, 62) THEN 2 END) AS NUMERIC_PRECISION_RADIX, convert(int, CASE WHEN c.system_type_id IN /* datetime/smalldatetime */ (58, 61) THEN NULL ELSE odbcscale(c.system_type_id, c.scale) END) AS NUMERIC_SCALE, convert(smallint, CASE WHEN c.system_type_id IN /* datetime/smalldatetime */ (58, 61) THEN 3 END) AS DATETIME_PRECISION, convert(sysname, null) AS CHARACTER_SET_CATALOG, convert(sysname, null) AS CHARACTER_SET_SCHEMA, convert(sysname, CASE WHEN c.system_type_id IN /* char/varchar/text */ (35, 167, 175) THEN CollationProperty(c.collation_name, 'sqlcharsetname') WHEN c.system_type_id IN /* nchar/nvarchar/ntext */ (99, 231, 239) THEN N'UNICODE' END) AS CHARACTER_SET_NAME, convert(sysname, null) AS COLLATION_CATALOG FROM sys.table_types tt join sys.objects o on o.object_id = tt.type_table_object_id JOIN sys.schemas sc on sc.schema_id = tt.schema_id JOIN sys.columns c ON c.object_id = o.object_id LEFT JOIN sys.types t ON c.user_type_id = t.user_type_id WHERE o.type IN ('TT') AND (DB_NAME() = @Catalog or (@Catalog is null)) and (sc.name = @Owner or (@Owner is null)) and (tt.name = @Type or (@Type is null)) and (c.name = @Member or (@Member is null)) order by sc.name, tt.name, c.name + 09.00.000.0 + + + Views + 3 + 3 + SQLCommand + select TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, CHECK_OPTION, IS_UPDATABLE from INFORMATION_SCHEMA.VIEWS where (TABLE_CATALOG = @Catalog or (@Catalog is null)) and (TABLE_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) order by TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME + 08.00.000.0 + + + ViewColumns + 4 + 4 + SQLCommand + select VIEW_CATALOG, VIEW_SCHEMA, VIEW_NAME, TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME from INFORMATION_SCHEMA.VIEW_COLUMN_USAGE where (VIEW_CATALOG = @Catalog or (@Catalog is null)) and (VIEW_SCHEMA = @Owner or (@Owner is null)) and (VIEW_NAME = @Table or (@Table is null)) and (COLUMN_NAME = @Column or (@Column is null)) order by VIEW_CATALOG, VIEW_SCHEMA, VIEW_NAME + 08.00.000.0 + + + ProcedureParameters + 4 + 1 + SQLCommand + select SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, ORDINAL_POSITION, PARAMETER_MODE, IS_RESULT, AS_LOCATOR, PARAMETER_NAME, CASE WHEN DATA_TYPE IS NULL THEN USER_DEFINED_TYPE_NAME WHEN DATA_TYPE = 'table type' THEN USER_DEFINED_TYPE_NAME ELSE DATA_TYPE END as DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH, COLLATION_CATALOG, COLLATION_SCHEMA, COLLATION_NAME, CHARACTER_SET_CATALOG, CHARACTER_SET_SCHEMA, CHARACTER_SET_NAME, NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION, INTERVAL_TYPE, INTERVAL_PRECISION from INFORMATION_SCHEMA.PARAMETERS where (SPECIFIC_CATALOG = @Catalog or (@Catalog is null)) and (SPECIFIC_SCHEMA = @Owner or (@Owner is null)) and (SPECIFIC_NAME = @Name or (@Name is null)) and (PARAMETER_NAME = @Parameter or (@Parameter is null)) order by SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, PARAMETER_NAME + 08.00.0000 + + + Procedures + 4 + 3 + SQLCommand + select SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME, ROUTINE_CATALOG, ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE, CREATED, LAST_ALTERED from INFORMATION_SCHEMA.ROUTINES where (SPECIFIC_CATALOG = @Catalog or (@Catalog is null)) and (SPECIFIC_SCHEMA = @Owner or (@Owner is null)) and (SPECIFIC_NAME = @Name or (@Name is null)) and (ROUTINE_TYPE = @Type or (@Type is null)) order by SPECIFIC_CATALOG, SPECIFIC_SCHEMA, SPECIFIC_NAME + 08.00.0000 + + + ForeignKeys + 4 + 3 + SQLCommand + select CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME, TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, CONSTRAINT_TYPE, IS_DEFERRABLE, INITIALLY_DEFERRED from INFORMATION_SCHEMA.TABLE_CONSTRAINTS where (CONSTRAINT_CATALOG = @Catalog or (@Catalog is null)) and (CONSTRAINT_SCHEMA = @Owner or (@Owner is null)) and (TABLE_NAME = @Table or (@Table is null)) and (CONSTRAINT_NAME = @Name or (@Name is null)) and CONSTRAINT_TYPE = 'FOREIGN KEY' order by CONSTRAINT_CATALOG, CONSTRAINT_SCHEMA, CONSTRAINT_NAME + + + IndexColumns + 5 + 4 + SQLCommand + select distinct db_Name() as constraint_catalog, constraint_schema = user_name(o.uid), constraint_name = x.name, table_catalog = db_name(), table_schema = user_name(o.uid), table_name = o.name, column_name = c.name, ordinal_position = convert(int, xk.keyno), KeyType = c.xtype, index_name = x.name from sysobjects o, sysindexes x, syscolumns c, sysindexkeys xk where o.type in ('U') and x.id = o.id and o.id = c.id and o.id = xk.id and x.indid = xk.indid and c.colid = xk.colid and xk.keyno < = x.keycnt and permissions(o.id, c.name) <> 0 and (db_name() = @Catalog or (@Catalog is null)) and (user_name()= @Owner or (@Owner is null)) and (o.name = @Table or (@Table is null)) and (x.name = @ConstraintName or (@ConstraintName is null)) and (c.name = @Column or (@Column is null)) order by table_name, index_name + 09.99.9999 + + + IndexColumns + 5 + 4 + SQLCommand + EXEC sys.sp_indexcolumns_managed @Catalog, @Owner, @Table, @ConstraintName, @Column + 10.00.0000 + + + Indexes + 4 + 3 + SQLCommand + select distinct db_Name() as constraint_catalog, constraint_schema = user_name(o.uid), constraint_name = x.name, table_catalog = db_name(), table_schema = user_name(o.uid), table_name = o.name, index_name = x.name from sysobjects o, sysindexes x, sysindexkeys xk where o.type in ('U') and x.id = o.id and o.id = xk.id and x.indid = xk.indid and xk.keyno < = x.keycnt and (db_name() = @Catalog or (@Catalog is null)) and (user_name()= @Owner or (@Owner is null)) and (o.name = @Table or (@Table is null)) and (x.name = @Name or (@Name is null)) order by table_name, index_name + 09.99.9999 + + + Indexes + 4 + 3 + SQLCommand + EXEC sys.sp_indexes_managed @Catalog, @Owner, @Table, @Name + 10.00.0000 + + + UserDefinedTypes + 2 + 1 + SQLCommand + select assemblies.name as assembly_name, types.assembly_class as udt_name, ASSEMBLYPROPERTY(assemblies.name, 'VersionMajor') as version_major, ASSEMBLYPROPERTY(assemblies.name, 'VersionMinor') as version_minor, ASSEMBLYPROPERTY(assemblies.name, 'VersionBuild') as version_build, ASSEMBLYPROPERTY(assemblies.name, 'VersionRevision') as version_revision, ASSEMBLYPROPERTY(assemblies.name, 'CultureInfo') as culture_info, ASSEMBLYPROPERTY(assemblies.name, 'PublicKey') as public_key, is_fixed_length, max_length, Create_Date, Permission_set_desc from sys.assemblies as assemblies join sys.assembly_types as types on assemblies.assembly_id = types.assembly_id where (assemblies.name = @AssemblyName or (@AssemblyName is null)) and (types.assembly_class = @UDTName or (@UDTName is null)) + 09.00.0000 + + + + + Users + User_Name + @Name + name + 1 + + + Databases + Name + @Name + Name + 1 + + + Tables + Catalog + @Catalog + TABLE_CATALOG + 1 + + + Tables + Owner + @Owner + TABLE_SCHEMA + 2 + + + Tables + Table + @Name + TABLE_NAME + 3 + + + Tables + TableType + @TableType + TABLE_TYPE + 4 + + + Columns + Catalog + @Catalog + TABLE_CATALOG + 1 + + + Columns + Owner + @Owner + TABLE_SCHEMA + 2 + + + Columns + Table + @Table + TABLE_NAME + 3 + + + Columns + Column + @Column + COLUMN_NAME + 4 + + + AllColumns + Catalog + @Catalog + TABLE_CATALOG + 1 + + + AllColumns + Owner + @Owner + TABLE_SCHEMA + 2 + + + AllColumns + Table + @Table + TABLE_NAME + 3 + + + AllColumns + Column + @Column + COLUMN_NAME + 4 + + + ColumnSetColumns + Catalog + @Catalog + TABLE_CATALOG + 1 + + + ColumnSetColumns + Owner + @Owner + TABLE_SCHEMA + 2 + + + ColumnSetColumns + Table + @Table + TABLE_NAME + 3 + + + StructuredTypeMembers + Catalog + @Catalog + TYPE_CATALOG + 1 + + + StructuredTypeMembers + Owner + @Owner + TYPE_SCHEMA + 2 + + + StructuredTypeMembers + Type + @Type + TYPE_NAME + 3 + + + StructuredTypeMembers + Member + @Member + MEMBER_NAME + 4 + + + Views + Catalog + @Catalog + TABLE_CATALOG + 1 + 08.00.000.0 + + + Views + Owner + @Owner + TABLE_SCHEMA + 2 + 08.00.000.0 + + + Views + Table + @Table + TABLE_NAME + 3 + 08.00.000.0 + + + ViewColumns + Catalog + @Catalog + VIEW_CATALOG + 1 + 08.00.000.0 + + + ViewColumns + Owner + @Owner + VIEW_SCHEMA + 2 + 08.00.000.0 + + + ViewColumns + Table + @Table + VIEW_NAME + 3 + 08.00.000.0 + + + ViewColumns + Column + @Column + COLUMN_NAME + 4 + 08.00.000.0 + + + ProcedureParameters + Catalog + @Catalog + SPECIFIC_CATALOG + 1 + 08.00.0000 + + + ProcedureParameters + Owner + @Owner + SPECIFIC_SCHEMA + 2 + 08.00.0000 + + + ProcedureParameters + Name + @Name + SPECIFIC_NAME + 3 + 08.00.0000 + + + ProcedureParameters + Parameter + @Parameter + PARAMETER_NAME + 4 + 08.00.0000 + + + Procedures + Catalog + @Catalog + SPECIFIC_CATALOG + 1 + 08.00.0000 + + + Procedures + Owner + @Owner + SPECIFIC_SCHEMA + 2 + 08.00.0000 + + + Procedures + Name + @Name + SPECIFIC_NAME + 3 + 08.00.0000 + + + Procedures + Type + @Type + ROUTINE_TYPE + 4 + 08.00.0000 + + + IndexColumns + Catalog + @Catalog + db_name() + 1 + + + IndexColumns + Owner + @Owner + user_name() + 2 + + + IndexColumns + Table + @Table + o.name + 3 + + + IndexColumns + ConstraintName + @ConstraintName + x.name + 4 + + + IndexColumns + Column + @Column + c.name + 5 + + + Indexes + Catalog + @Catalog + db_name() + 1 + + + Indexes + Owner + @Owner + user_name() + 2 + + + Indexes + Table + @Table + o.name + 3 + + + Indexes + Name + @Name + x.name + 4 + + + UserDefinedTypes + assembly_name + @AssemblyName + assemblies.name + 1 + 09.00.000.0 + + + UserDefinedTypes + udt_name + @UDTName + types.assembly_class + 2 + 09.00.000.0 + + + ForeignKeys + Catalog + @Catalog + CONSTRAINT_CATALOG + 1 + + + ForeignKeys + Owner + @Owner + CONSTRAINT_SCHEMA + 2 + + + ForeignKeys + Table + @Table + TABLE_NAME + 3 + + + ForeignKeys + Name + @Name + CONSTRAINT_NAME + 4 + + + + + \. + Microsoft SQL Server + 2 + (^\[\p{Lo}\p{Lu}\p{Ll}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Nd}@$#_]*$)|(^\[[^\]\0]|\]\]+\]$)|(^\"[^\"\0]|\"\"+\"$) + 1 + false + {0} + @[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$) + 128 + ^[\p{Lo}\p{Lu}\p{Ll}\p{Lm}_@#][\p{Lo}\p{Lu}\p{Ll}\p{Lm}\p{Nd}\uff3f_@#\$]*(?=\s+|$) + (([^\[]|\]\])*) + 1 + ; + '(([^']|'')*)' + 15 + + + + + smallint + 16 + 5 + smallint + System.Int16 + true + true + false + false + true + true + false + true + true + false + false + + + int + 8 + 10 + int + System.Int32 + true + true + false + false + true + true + false + true + true + false + false + + + real + 13 + 7 + real + System.Single + false + true + false + false + true + false + false + true + true + false + false + + + float + 6 + 53 + float({0}) + number of bits used to store the mantissa + System.Double + false + true + false + false + true + false + false + true + true + false + false + + + money + 9 + 19 + money + System.Decimal + false + false + false + false + true + true + false + true + true + false + false + + + smallmoney + 17 + 10 + smallmoney + System.Decimal + false + false + false + false + true + true + false + true + true + false + false + + + bit + 2 + 1 + bit + System.Boolean + false + false + false + false + true + false + false + true + true + false + + + tinyint + 20 + 3 + tinyint + System.Byte + true + true + false + false + true + true + false + true + true + false + true + + + bigint + 0 + 19 + bigint + System.Int64 + true + true + false + false + true + true + false + true + true + false + false + + + varbinary + 21 + 8000 + varbinary({0}) + max length + System.Byte[] + false + true + false + false + false + false + false + true + true + false + 08.99.999.9 + 0x + + + timestamp + 19 + 8 + timestamp + System.Byte[] + false + false + false + true + true + false + false + false + true + false + 0x + + + binary + 1 + 8000 + binary({0}) + length + System.Byte[] + false + true + false + false + true + false + false + true + true + false + 0x + + + image + 7 + 2147483647 + image + System.Byte[] + false + true + false + false + false + false + true + true + false + false + 0x + + + char + 3 + 8000 + char + length + System.String + false + true + false + false + true + false + false + true + true + true + 08.99.999.9 + ' + ' + + + text + 18 + 2147483647 + text + System.String + false + true + false + false + false + false + true + true + false + true + ' + ' + + + varchar + 22 + 8000 + varchar({0}) + max length + System.String + false + true + false + false + false + false + false + true + true + true + 08.99.999.9 + ' + ' + + + nchar + 10 + 4000 + nchar({0}) + length + System.String + false + true + false + false + true + false + false + true + true + true + 08.99.999.9 + N' + ' + + + ntext + 11 + 1073741823 + ntext + System.String + false + true + false + false + false + false + true + true + false + true + N' + ' + + + nvarchar + 12 + 4000 + nvarchar({0}) + max length + System.String + false + true + false + false + false + false + false + true + true + true + 08.99.999.9 + N' + ' + + + decimal + 5 + 38 + decimal({0}, {1}) + precision,scale + System.Decimal + true + true + false + false + true + false + false + true + true + false + false + 38 + 0 + + + numeric + 5 + 38 + numeric({0}, {1}) + precision,scale + System.Decimal + true + true + false + false + true + false + false + true + true + false + false + 38 + 0 + + + datetime + 4 + 23 + datetime + System.DateTime + false + true + false + false + true + false + false + true + true + true + {ts ' + '} + + + smalldatetime + 15 + 16 + smalldatetime + System.DateTime + false + true + false + false + true + false + false + true + true + true + {ts ' + '} + + + sql_variant + 23 + sql_variant + System.Object + false + true + false + false + false + false + false + true + true + false + false + + + xml + 25 + 2147483647 + xml + System.String + false + false + false + false + false + false + true + true + false + false + 09.00.000.0 + false + + + varchar + 22 + 2147483647 + varchar({0}) + max length + System.String + false + true + false + false + false + false + false + true + true + true + 09.00.000.0 + ' + ' + + + char + 3 + 2147483647 + char({0}) + length + System.String + false + true + false + false + true + false + false + true + true + true + 09.00.000.0 + ' + ' + + + nchar + 10 + 1073741823 + nchar({0}) + length + System.String + false + true + false + false + true + false + false + true + true + true + 09.00.000.0 + N' + ' + + + nvarchar + 12 + 1073741823 + nvarchar({0}) + max length + System.String + false + true + false + false + false + false + false + true + true + true + 09.00.000.0 + N' + ' + + + varbinary + 21 + 1073741823 + varbinary({0}) + max length + System.Byte[] + false + true + false + false + false + false + false + true + true + false + 09.00.000.0 + 0x + + + uniqueidentifier + 14 + 16 + uniqueidentifier + System.Guid + false + true + false + false + true + false + false + true + true + false + ' + ' + + + date + 31 + 3 + date + System.DateTime + false + false + false + false + true + true + false + true + true + true + 10.00.000.0 + {ts ' + '} + + + time + 32 + 5 + time({0}) + scale + System.TimeSpan + false + false + false + false + false + false + false + true + true + true + 7 + 0 + 10.00.000.0 + {ts ' + '} + + + datetime2 + 33 + 8 + datetime2({0}) + scale + System.DateTime + false + true + false + false + false + false + false + true + true + true + 7 + 0 + 10.00.000.0 + {ts ' + '} + + + datetimeoffset + 34 + 10 + datetimeoffset({0}) + scale + System.DateTimeOffset + false + true + false + false + false + false + false + true + true + true + 7 + 0 + 10.00.000.0 + {ts ' + '} + + + + + ADD + + + EXCEPT + + + PERCENT + + + ALL + + + EXEC + + + PLAN + + + ALTER + + + EXECUTE + + + PRECISION + + + AND + + + EXISTS + + + PRIMARY + + + ANY + + + EXIT + + + PRINT + + + AS + + + FETCH + + + PROC + + + ASC + + + FILE + + + PROCEDURE + + + AUTHORIZATION + + + FILLFACTOR + + + PUBLIC + + + BACKUP + + + FOR + + + RAISERROR + + + BEGIN + + + FOREIGN + + + READ + + + BETWEEN + + + FREETEXT + + + READTEXT + + + BREAK + + + FREETEXTTABLE + + + RECONFIGURE + + + BROWSE + + + FROM + + + REFERENCES + + + BULK + + + FULL + + + REPLICATION + + + BY + + + FUNCTION + + + RESTORE + + + CASCADE + + + GOTO + + + RESTRICT + + + CASE + + + GRANT + + + RETURN + + + CHECK + + + GROUP + + + REVOKE + + + CHECKPOINT + + + HAVING + + + RIGHT + + + CLOSE + + + HOLDLOCK + + + ROLLBACK + + + CLUSTERED + + + IDENTITY + + + ROWCOUNT + + + COALESCE + + + IDENTITY_INSERT + + + ROWGUIDCOL + + + COLLATE + + + IDENTITYCOL + + + RULE + + + COLUMN + + + IF + + + SAVE + + + COMMIT + + + IN + + + SCHEMA + + + COMPUTE + + + INDEX + + + SELECT + + + CONSTRAINT + + + INNER + + + SESSION_USER + + + CONTAINS + + + INSERT + + + SET + + + CONTAINSTABLE + + + INTERSECT + + + SETUSER + + + CONTINUE + + + INTO + + + SHUTDOWN + + + CONVERT + + + IS + + + SOME + + + CREATE + + + JOIN + + + STATISTICS + + + CROSS + + + KEY + + + SYSTEM_USER + + + CURRENT + + + KILL + + + TABLE + + + CURRENT_DATE + + + LEFT + + + TEXTSIZE + + + CURRENT_TIME + + + LIKE + + + THEN + + + CURRENT_TIMESTAMP + + + LINENO + + + TO + + + CURRENT_USER + + + LOAD + + + TOP + + + CURSOR + + + NATIONAL + + + TRAN + + + DATABASE + + + NOCHECK + + + TRANSACTION + + + DBCC + + + NONCLUSTERED + + + TRIGGER + + + DEALLOCATE + + + NOT + + + TRUNCATE + + + DECLARE + + + NULL + + + TSEQUAL + + + DEFAULT + + + NULLIF + + + UNION + + + DELETE + + + OF + + + UNIQUE + + + DENY + + + OFF + + + UPDATE + + + DESC + + + OFFSETS + + + UPDATETEXT + + + DISK + + + ON + + + USE + + + DISTINCT + + + OPEN + + + USER + + + DISTRIBUTED + + + OPENDATASOURCE + + + VALUES + + + DOUBLE + + + OPENQUERY + + + VARYING + + + DROP + + + OPENROWSET + + + VIEW + + + DUMMY + + + OPENXML + + + WAITFOR + + + DUMP + + + OPTION + + + WHEN + + + ELSE + + + OR + + + WHERE + + + END + + + ORDER + + + WHILE + + + ERRLVL + + + OUTER + + + WITH + + + ESCAPE + + + OVER + + + WRITETEXT + + + ABSOLUTE + + + FOUND + + + PRESERVE + + + ACTION + + + FREE + + + PRIOR + + + ADMIN + + + GENERAL + + + PRIVILEGES + + + AFTER + + + GET + + + READS + + + AGGREGATE + + + GLOBAL + + + REAL + + + ALIAS + + + GO + + + RECURSIVE + + + ALLOCATE + + + GROUPING + + + REF + + + ARE + + + HOST + + + REFERENCING + + + ARRAY + + + HOUR + + + RELATIVE + + + ASSERTION + + + IGNORE + + + RESULT + + + AT + + + IMMEDIATE + + + RETURNS + + + BEFORE + + + INDICATOR + + + ROLE + + + BINARY + + + INITIALIZE + + + ROLLUP + + + BIT + + + INITIALLY + + + ROUTINE + + + BLOB + + + INOUT + + + ROW + + + BOOLEAN + + + INPUT + + + ROWS + + + BOTH + + + INT + + + SAVEPOINT + + + BREADTH + + + INTEGER + + + SCROLL + + + CALL + + + INTERVAL + + + SCOPE + + + CASCADED + + + ISOLATION + + + SEARCH + + + CAST + + + ITERATE + + + SECOND + + + CATALOG + + + LANGUAGE + + + SECTION + + + CHAR + + + LARGE + + + SEQUENCE + + + CHARACTER + + + LAST + + + SESSION + + + CLASS + + + LATERAL + + + SETS + + + CLOB + + + LEADING + + + SIZE + + + COLLATION + + + LESS + + + SMALLINT + + + COMPLETION + + + LEVEL + + + SPACE + + + CONNECT + + + LIMIT + + + SPECIFIC + + + CONNECTION + + + LOCAL + + + SPECIFICTYPE + + + CONSTRAINTS + + + LOCALTIME + + + SQL + + + CONSTRUCTOR + + + LOCALTIMESTAMP + + + SQLEXCEPTION + + + CORRESPONDING + + + LOCATOR + + + SQLSTATE + + + CUBE + + + MAP + + + SQLWARNING + + + CURRENT_PATH + + + MATCH + + + START + + + CURRENT_ROLE + + + MINUTE + + + STATE + + + CYCLE + + + MODIFIES + + + STATEMENT + + + DATA + + + MODIFY + + + STATIC + + + DATE + + + MODULE + + + STRUCTURE + + + DAY + + + MONTH + + + TEMPORARY + + + DEC + + + NAMES + + + TERMINATE + + + DECIMAL + + + NATURAL + + + THAN + + + DEFERRABLE + + + NCHAR + + + TIME + + + DEFERRED + + + NCLOB + + + TIMESTAMP + + + DEPTH + + + NEW + + + TIMEZONE_HOUR + + + DEREF + + + NEXT + + + TIMEZONE_MINUTE + + + DESCRIBE + + + NO + + + TRAILING + + + DESCRIPTOR + + + NONE + + + TRANSLATION + + + DESTROY + + + NUMERIC + + + TREAT + + + DESTRUCTOR + + + OBJECT + + + TRUE + + + DETERMINISTIC + + + OLD + + + UNDER + + + DICTIONARY + + + ONLY + + + UNKNOWN + + + DIAGNOSTICS + + + OPERATION + + + UNNEST + + + DISCONNECT + + + ORDINALITY + + + USAGE + + + DOMAIN + + + OUT + + + USING + + + DYNAMIC + + + OUTPUT + + + VALUE + + + EACH + + + PAD + + + VARCHAR + + + END-EXEC + + + PARAMETER + + + VARIABLE + + + EQUALS + + + PARAMETERS + + + WHENEVER + + + EVERY + + + PARTIAL + + + WITHOUT + + + EXCEPTION + + + PATH + + + WORK + + + EXTERNAL + + + POSTFIX + + + WRITE + + + FALSE + + + PREFIX + + + YEAR + + + FIRST + + + PREORDER + + + ZONE + + + FLOAT + + + PREPARE + + + ADA + + + AVG + + + BIT_LENGTH + + + CHAR_LENGTH + + + CHARACTER_LENGTH + + + COUNT + + + EXTRACT + + + FORTRAN + + + INCLUDE + + + INSENSITIVE + + + LOWER + + + MAX + + + MIN + + + OCTET_LENGTH + + + OVERLAPS + + + PASCAL + + + POSITION + + + SQLCA + + + SQLCODE + + + SQLERROR + + + SUBSTRING + + + SUM + + + TRANSLATE + + + TRIM + + + UPPER + + + diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs index 1bcd4ff0be..9f460e7c04 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.Designer.cs @@ -240,15 +240,6 @@ internal static string ADP_ComputerNameEx { } } - /// - /// Looks up a localized string similar to Update requires a connection.. - /// - internal static string ADP_ConnecitonRequired_UpdateRows { - get { - return ResourceManager.GetString("ADP_ConnecitonRequired_UpdateRows", resourceCulture); - } - } - /// /// Looks up a localized string similar to The connection was not closed. {0}. /// @@ -348,6 +339,15 @@ internal static string ADP_ConnectionRequired_Update { } } + /// + /// Looks up a localized string similar to Update requires a connection.. + /// + internal static string ADP_ConnectionRequired_UpdateRows { + get { + return ResourceManager.GetString("ADP_ConnectionRequired_UpdateRows", resourceCulture); + } + } + /// /// Looks up a localized string similar to The connection's current state: {0}.. /// @@ -1301,7 +1301,18 @@ internal static string ADP_NoQuoteChange { return ResourceManager.GetString("ADP_NoQuoteChange", resourceCulture); } } - + + /// + /// Looks up a localized string similar to SqlBatchCommand list has not been initialized.. + /// + internal static string ADP_NoSqlBatchCommandList + { + get + { + return ResourceManager.GetString("ADP_NoSqlBatchCommandList", resourceCulture); + } + } + /// /// Looks up a localized string similar to The stored procedure '{0}' doesn't exist.. /// @@ -1897,47 +1908,29 @@ internal static string ArgumentOutOfRange_NeedNonNegNum { } /// - /// Looks up a localized string similar to The validation of an attestation token failed. The token signature does not match the computed signature using a public key retrieved from the attestation public key endpoint at '{0}'. Verify the DNS mapping for the endpoint - see https://go.microsoft.com/fwlink/?linkid=2157649 for more details. If correct, contact Customer Support Services.. + /// Looks up a localized string similar to Buffer advanced too far {0}. /// - internal static string AttestationTokenSignatureValidationFailed { + internal static string ArrayBufferWriterAdvancedTooFar { get { - return ResourceManager.GetString("AttestationTokenSignatureValidationFailed", resourceCulture); + return ResourceManager.GetString("ArrayBufferWriterAdvancedTooFar", resourceCulture); } } /// - /// Looks up a localized string similar to .database.chinacloudapi.cn. + /// Looks up a localized string similar to Buffer maximum size exceeded: {0}. /// - internal static string AZURESQL_ChinaEndpoint { + internal static string ArrayBufferWriterMaximumSizeExceeded { get { - return ResourceManager.GetString("AZURESQL_ChinaEndpoint", resourceCulture); + return ResourceManager.GetString("ArrayBufferWriterMaximumSizeExceeded", resourceCulture); } } /// - /// Looks up a localized string similar to .database.windows.net. - /// - internal static string AZURESQL_GenericEndpoint { - get { - return ResourceManager.GetString("AZURESQL_GenericEndpoint", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to .database.cloudapi.de. - /// - internal static string AZURESQL_GermanEndpoint { - get { - return ResourceManager.GetString("AZURESQL_GermanEndpoint", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to .database.usgovcloudapi.net. + /// Looks up a localized string similar to The validation of an attestation token failed. The token signature does not match the computed signature using a public key retrieved from the attestation public key endpoint at '{0}'. Verify the DNS mapping for the endpoint - see https://go.microsoft.com/fwlink/?linkid=2157649 for more details. If correct, contact Customer Support Services.. /// - internal static string AZURESQL_UsGovEndpoint { + internal static string AttestationTokenSignatureValidationFailed { get { - return ResourceManager.GetString("AZURESQL_UsGovEndpoint", resourceCulture); + return ResourceManager.GetString("AttestationTokenSignatureValidationFailed", resourceCulture); } } @@ -2295,9 +2288,9 @@ internal static string DataCategory_Behavior { /// /// Looks up a localized string similar to Connection Resiliency. /// - internal static string DataCategory_ConnectionResilency { + internal static string DataCategory_ConnectionResiliency { get { - return ResourceManager.GetString("DataCategory_ConnectionResilency", resourceCulture); + return ResourceManager.GetString("DataCategory_ConnectionResiliency", resourceCulture); } } @@ -2808,9 +2801,9 @@ internal static string DataColumn_NullValues { /// /// Looks up a localized string similar to Ordinal '{0}' exceeds the maximum number.. /// - internal static string DataColumn_OrdinalExceedMaximun { + internal static string DataColumn_OrdinalExceedMaximum { get { - return ResourceManager.GetString("DataColumn_OrdinalExceedMaximun", resourceCulture); + return ResourceManager.GetString("DataColumn_OrdinalExceedMaximum", resourceCulture); } } @@ -3681,9 +3674,9 @@ internal static string DataMerge_PrimaryKeyMismatch { /// /// Looks up a localized string similar to Relation {0} cannot be merged, because keys have mismatch columns.. /// - internal static string DataMerge_ReltionKeyColumnsMismatch { + internal static string DataMerge_RelationKeyColumnsMismatch { get { - return ResourceManager.GetString("DataMerge_ReltionKeyColumnsMismatch", resourceCulture); + return ResourceManager.GetString("DataMerge_RelationKeyColumnsMismatch", resourceCulture); } } @@ -6354,18 +6347,18 @@ internal static string Expr_ComputeNotAggregate { /// /// Looks up a localized string similar to Cannot convert from {0} to {1}.. /// - internal static string Expr_DatatypeConvertion { + internal static string Expr_DatatypeConversion { get { - return ResourceManager.GetString("Expr_DatatypeConvertion", resourceCulture); + return ResourceManager.GetString("Expr_DatatypeConversion", resourceCulture); } } /// /// Looks up a localized string similar to Cannot convert value '{0}' to Type: {1}.. /// - internal static string Expr_DatavalueConvertion { + internal static string Expr_DatavalueConversion { get { - return ResourceManager.GetString("Expr_DatavalueConvertion", resourceCulture); + return ResourceManager.GetString("Expr_DatavalueConversion", resourceCulture); } } @@ -6408,9 +6401,9 @@ internal static string Expr_ExpressionUnbound { /// /// Looks up a localized string similar to Filter expression '{0}' does not evaluate to a Boolean term.. /// - internal static string Expr_FilterConvertion { + internal static string Expr_FilterConversion { get { - return ResourceManager.GetString("Expr_FilterConvertion", resourceCulture); + return ResourceManager.GetString("Expr_FilterConversion", resourceCulture); } } @@ -7182,9 +7175,9 @@ internal static string MissingClaimInAttestationToken { /// /// Looks up a localized string similar to Simple type '{0}' has already be declared with different '{1}'.. /// - internal static string NamedSimpleType_InvalidDuplicateNamedSimpleTypeDelaration { + internal static string NamedSimpleType_InvalidDuplicateNamedSimpleTypeDeclaration { get { - return ResourceManager.GetString("NamedSimpleType_InvalidDuplicateNamedSimpleTypeDelaration", resourceCulture); + return ResourceManager.GetString("NamedSimpleType_InvalidDuplicateNamedSimpleTypeDeclaration", resourceCulture); } } @@ -8344,7 +8337,7 @@ internal static string SNI_ERROR_1 { } /// - /// Looks up a localized string similar to . + /// Looks up a localized string similar to Socket is null. /// internal static string SNI_ERROR_10 { get { @@ -9039,9 +9032,9 @@ internal static string Snix_PreLogin { /// /// Looks up a localized string similar to The client was unable to establish a connection because of an error during connection initialization process before login. Possible causes include the following: the client tried to connect to an unsupported version of SQL Server; the server was too busy to accept new connections; or there was a resource limitation (insufficient memory or maximum allowed connections) on the server.. /// - internal static string Snix_PreLoginBeforeSuccessfullWrite { + internal static string Snix_PreLoginBeforeSuccessfulWrite { get { - return ResourceManager.GetString("Snix_PreLoginBeforeSuccessfullWrite", resourceCulture); + return ResourceManager.GetString("Snix_PreLoginBeforeSuccessfulWrite", resourceCulture); } } @@ -9485,18 +9478,16 @@ internal static string SQL_ContextConnectionIsInUse { return ResourceManager.GetString("SQL_ContextConnectionIsInUse", resourceCulture); } } - + /// /// Looks up a localized string similar to Connecting to the context connection using Microsoft.Data.SqlClient is not supported.. /// - internal static string SQL_ContextConnectionIsUnsupported - { - get - { + internal static string SQL_ContextConnectionIsUnsupported { + get { return ResourceManager.GetString("SQL_ContextConnectionIsUnsupported", resourceCulture); } } - + /// /// Looks up a localized string similar to The requested operation requires a SqlClr context, which is only available when running in the Sql Server process.. /// @@ -9686,6 +9677,15 @@ internal static string SQL_ExRoutingDestination { } } + /// + /// Looks up a localized string similar to: The connection attempt timed out. + /// + internal static string SQL_ConnectTimeout { + get { + return ResourceManager.GetString("SQL_ConnectTimeout", resourceCulture); + } + } + /// /// Looks up a localized string similar to Timeout expired. The connection has been broken as a result.. /// @@ -9884,6 +9884,15 @@ internal static string SQL_InvalidUdt3PartNameFormat { } } + /// + /// Looks up a localized string similar to Invalid attempt to get JsonDocument on column '{0}'. JsonDocument is only supported for columns of type json.. + /// + internal static string SQL_JsonDocumentNotSupportedOnColumnType { + get { + return ResourceManager.GetString("SQL_JsonDocumentNotSupportedOnColumnType", resourceCulture); + } + } + /// /// Looks up a localized string similar to Cannot authenticate using Kerberos. Ensure Kerberos has been initialized on the client with 'kinit' and a Service Principal Name has been registered for the SQL Server to allow Kerberos authentication.. /// @@ -10495,16 +10504,7 @@ internal static string SQL_StreamNotSupportOnColumnType { return ResourceManager.GetString("SQL_StreamNotSupportOnColumnType", resourceCulture); } } - - /// - /// Looks up a localized string similar to Invalid attempt to get JsonDocument on column '{0}'. JsonDocument is only supported for columns of type json. - /// - internal static string SQL_JsonDocumentNotSupportedOnColumnType { - get { - return ResourceManager.GetString("SQL_JsonDocumentNotSupportedOnColumnType", resourceCulture); - } - } - + /// /// Looks up a localized string similar to The Stream does not support reading.. /// @@ -10659,7 +10659,7 @@ internal static string SQL_Timeout_PreLogin_SendHandshake { } /// - /// Looks up a localized string similar to This failure occurred while attempting to connect to the routing destination. The duration spent while attempting to connect to the original server was - [Pre-Login] initialization={0}; handshake={1}; [Login] initialization={2}; authentication={3}; [Post-Login] complete={4}; . + /// Looks up a localized string similar to This failure occurred while attempting to connect to the routing destination. The duration spent while attempting to connect to the original server was - [Pre-Login] initialization={0}; handshake={1}; [Login] initialization={2}; authentication={3}; [Post-Login] complete={4}; . /// internal static string SQL_Timeout_RoutingDestinationInfo { get { @@ -10830,10 +10830,65 @@ internal static string SQL_UserInstanceNotAvailableInProc { } /// - /// Looks up a localized string similar to Expecting argument of type {1}, but received type {0}.. + /// Looks up a localized string similar to Invalid attempt to get vector data from column '{0}'. Vectors are only supported for columns of type vector.. /// - internal static string SQL_WrongType { + internal static string SQL_VectorNotSupportedOnColumnType { get { + return ResourceManager.GetString("SQL_VectorNotSupportedOnColumnType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unsupported Vector type '{0}'.. + /// + internal static string SQL_VectorTypeNotSupported + { + get + { + return ResourceManager.GetString("SQL_VectorTypeNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 'null' value not supported for output paramter {0} of SqlDbtype Vector.. + /// + internal static string ADP_NullOutputParameterValueForVector + { + get + { + return ResourceManager.GetString("ADP_NullOutputParameterValueForVector", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid vector header received.. + /// + internal static string ADP_InvalidVectorHeader + { + get + { + return ResourceManager.GetString("ADP_InvalidVectorHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} Invalid JSON string for vector... + /// + internal static string ADP_InvalidJsonStringForVector + { + get + { + return ResourceManager.GetString("ADP_InvalidJsonStringForVector", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Expecting argument of type {1}, but received type {0}.. + /// + internal static string SQL_WrongType + { + get + { return ResourceManager.GetString("SQL_WrongType", resourceCulture); } } @@ -11111,9 +11166,9 @@ internal static string SQLCR_NoCRAckAtReconnection { /// /// Looks up a localized string similar to The server did not preserve the exact client TDS version requested during a recovery attempt, connection recovery is not possible.. /// - internal static string SQLCR_TDSVestionNotPreserved { + internal static string SQLCR_TDSVersionNotPreserved { get { - return ResourceManager.GetString("SQLCR_TDSVestionNotPreserved", resourceCulture); + return ResourceManager.GetString("SQLCR_TDSVersionNotPreserved", resourceCulture); } } @@ -11982,7 +12037,7 @@ internal static string SQLROR_InvalidRoutingInfo { } /// - /// Looks up a localized string similar to Two or more redirections have occurred. Only one redirection per login is allowed.. + /// Looks up a localized string similar to Too many redirections have occurred. Only {0} redirections per login is allowed.. /// internal static string SQLROR_RecursiveRoutingNotSupported { get { @@ -12155,9 +12210,9 @@ internal static string SqlUdtReason_MultivaluedAssemblyId { /// /// Looks up a localized string similar to The type of field '{0}' is marked as explicit layout which is not allowed in Native format. /// - internal static string SqlUdtReason_NativeFormatExplictLayoutNotAllowed { + internal static string SqlUdtReason_NativeFormatExplicitLayoutNotAllowed { get { - return ResourceManager.GetString("SqlUdtReason_NativeFormatExplictLayoutNotAllowed", resourceCulture); + return ResourceManager.GetString("SqlUdtReason_NativeFormatExplicitLayoutNotAllowed", resourceCulture); } } @@ -12290,27 +12345,27 @@ internal static string SSPIInvalidHandleType { /// /// Looks up a localized string similar to Cannot get value because it is DBNull.. /// - internal static string StrongTyping_CananotAccessDBNull { + internal static string StrongTyping_CannotAccessDBNull { get { - return ResourceManager.GetString("StrongTyping_CananotAccessDBNull", resourceCulture); + return ResourceManager.GetString("StrongTyping_CannotAccessDBNull", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove relation since it is built in to this dataSet.. + /// Looks up a localized string similar to Cannot remove column since it is built in to this dataSet.. /// - internal static string StrongTyping_CananotRemoveRelation { + internal static string StrongTyping_CannotRemoveColumn { get { - return ResourceManager.GetString("StrongTyping_CananotRemoveRelation", resourceCulture); + return ResourceManager.GetString("StrongTyping_CannotRemoveColumn", resourceCulture); } } /// - /// Looks up a localized string similar to Cannot remove column since it is built in to this dataSet.. + /// Looks up a localized string similar to Cannot remove relation since it is built in to this dataSet.. /// - internal static string StrongTyping_CannotRemoveColumn { + internal static string StrongTyping_CannotRemoveRelation { get { - return ResourceManager.GetString("StrongTyping_CannotRemoveColumn", resourceCulture); + return ResourceManager.GetString("StrongTyping_CannotRemoveRelation", resourceCulture); } } @@ -13919,9 +13974,9 @@ internal static string Xml_SimpleTypeNotSupported { /// /// Looks up a localized string similar to Cannot determine the DataSet Element. IsDataSet attribute exist more than once.. /// - internal static string Xml_TooManyIsDataSetAtributeInSchema { + internal static string Xml_TooManyIsDataSetAttributeInSchema { get { - return ResourceManager.GetString("Xml_TooManyIsDataSetAtributeInSchema", resourceCulture); + return ResourceManager.GetString("Xml_TooManyIsDataSetAttributeInSchema", resourceCulture); } } diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx index 1e60d78eae..7fc79f1934 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.cs.resx @@ -231,18 +231,12 @@ Nelze použít pověření (Credential) s klíčovým slovem Integrated Security, které může být součástí připojovacího řetězce. - - Nelze použít pověření (Credential) s klíčovým slovem Context Connection. - Pokud je v připojovacím řetězci zadané UserID, UID, Password nebo PWD, nejde vlastnost AccessToken nastavit. Pokud je klíčové slovo Integrated Security připojovacího řetězce nastavené na hodnotu true nebo SSPI, nejde vlastnost AccessToken nastavit. - - Vlastnost AccessToken nejde nastavit s klíčovým slovem Context Connection. - Pokud už je nastavená vlastnost Credential, nejde vlastnost AccessToken nastavit. @@ -1845,9 +1839,6 @@ Neshodné typy SourceColumn {0} z(e) {1} a DataColumn {2} z(e) {3} nelze převést. - - Nebyl nalezen objekt DataColumn {0} pro SourceColumn {2}. - V objektu DataTable {1} pro vlastnost SourceColumn {2} chybí objekt DataColumn {0}. @@ -2076,9 +2067,6 @@ Neplatný posun cílové vyrovnávací paměti (velikost: {0}, posun: {1}) - - Pro řádek nebo sloupec neexistují data. - Číselná hodnota je příliš velká. Nemůže ji obsahovat desítková 96bitová hodnota. @@ -2532,6 +2520,9 @@ Tento příkaz vyžaduje asynchronní připojení. Nastavte v připojovacím řetězci hodnotu Asynchronous Processing=true. + + Časový limit pokusu o připojení vypršel. + Časový limit vypršel. V důsledku toho bylo připojení přerušeno. @@ -2781,30 +2772,6 @@ Délka dat {0} je menší než 0. - - Neplatný pokus o volání metody {0}, pokud je zavřena třída SqlResultSet. - - - Operaci nelze dokončit, protože třída SqlResultSet je zavřena. - - - {0} nelze volat, pokud je záznam určen jen pro čtení. - - - Operaci nelze dokončit, protože záznam je jen pro čtení. - - - Neplatný pokus o volání metody {0}, pokud je aktuální řádek odstraněn. - - - Operaci nelze dokončit, protože aktuální řádek je odstraněn. - - - Operaci nelze dokončit, protože příkaz, který vytvořil objekt SqlResultSet, byl odpojen od původního připojení. Objekt SqlResultSet je uzavřen. - - - SqlResultSet nebylo možné vytvořit pro daný dotaz s požadovanými možnostmi. - {0} nelze volat, pokud je třída SqlDataRecord určena jen pro čtení. @@ -2874,51 +2841,21 @@ Aktuálně načtená knihovna System.Transactions.dll nepodporuje globální transakce. Upgradujte prosím na .NET Framework 4.6.2 nebo novější. - - Dávkové aktualizace nejsou u připojení kontextu podporovány. - - - Jediné další klíčové slovo řetězce připojení, které by bylo možné používat při požadavku na připojení kontextu, je klíčové slovo Type System Version. - - - Připojení kontextu nepodporuje nastavení Type System Version=SQL Server 2000. - Připojení ke kontextu pomocí Microsoft.Data.SqlClient není podporováno. - - Připojení kontextu se už používá. - - - Požadovaná operace vyžaduje kontext SqlClr, který je k dispozici pouze při spuštění v procesu systému Sql Server. - - - Požadovaná operace vyžaduje vlákno prováděného procesu systému Sql Server. Aktuální vlákno bylo spuštěno uživatelským kódem či jiným kódem, který není kódem modulu systému Sql Server. - Vnořené objekty TransactionScopes nejsou podporovány. - - Požadovaná operace není dostupná u připojení kontextu. - - - Upozornění nejsou k dispozici u připojení kontextu. - Neočekávaná událost serveru: {0} - - Uživatelské instance nejsou povoleny při spuštění v procesu systému Sql Server. - Délka argumentu {0} se musí shodovat s délkou argumentu {1}. Hodnota SqlDbType {0} je pro {1} neplatná. Podporuje se pouze {2}. - - U předcházejícího řádku odeslaného do SqlPipe došlo k chybě. Před odesláním dalších dat se musí volat metoda SendResultsEnd. - Příliš mnoho hodnot @@ -3666,9 +3603,6 @@ Tento objekt SqlCommand je již spojen s jiným objektem SqlDependency. - - Objekt SqlDependency nelze vytvořit při spuštění z procesu systému SQL Server. - SQL Server Service Broker pro aktuální databázi není povolen. Nejsou proto podporována upozornění na výsledky dotazů. Chcete-li používat upozornění, povolte zprostředkovatele Service Broker pro tuto databázi. @@ -3729,21 +3663,6 @@ Ve výčtu SqlDataRecord nejsou žádné záznamy. Pokud chcete odeslat parametr vracející tabulku bez řádků, použijte pro hodnotu odkaz s hodnotou null. - - Třída SqlPipe nepodporuje provádění příkazu s připojením, které není připojením kontextu. - - - Délka zprávy {0} překračuje maximální podporovanou délku 4000. - - - Kanál nelze používat, pokud zpracovává jinou operaci. - - - Sada výsledků dotazu je momentálně odesílána do kanálu. Před voláním metody {0} ukončete aktuální sadu výsledků dotazu. - - - Sada výsledků dotazu nebyla inicializována. Před voláním metody {0} volejte metodu SendResultSetStart. - Zprostředkovatel HTTP: @@ -3808,7 +3727,7 @@ Nezdařilo se asociování portu se vstupně-výstupním (I/O) mechanismem dokončení. - + Soket má hodnotu null Chyba časového limitu @@ -4446,18 +4365,6 @@ Definuje chování v období blokování pro fond připojení. - - .database.windows.net - - - .database.cloudapi.de - - - .database.usgovcloudapi.net - - - .database.chinacloudapi.cn - Definuje, zda je povolené ukládání metadat dotazu do mezipaměti. @@ -4746,4 +4653,31 @@ Neplatný pokus o získání třídy JsonDocument ve sloupci {0}. Třída JsonDocument se podporuje jenom pro sloupce typu json. + + Velikost vyrovnávací paměti se příliš zvýšila {0} + + + Byla překročena maximální velikost vyrovnávací paměti: {0} + + + Nebyl nalezen objekt DataColumn {0} pro SourceColumn {2}. + + + Seznam SqlBatchCommand nebyl inicializován. + + + Pokus o načtení vektorových dat ze sloupce {0} byl neplatný. Vektory jsou podporovány pouze pro sloupce typu vektor. + + + Nepodporovaný typ vektoru „{0}“. + + + Hodnota null není podporována pro výstupní parametr „{0}“ vektoru SqlDbtype. + + + Byla přijata neplatná hlavička vektoru. + + + {0}: Neplatný řetězec JSON pro vektor. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx index 9a2d968e17..07411316df 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.de.resx @@ -231,18 +231,12 @@ Credential kann nicht mit dem Schlüsselwort für Verbindungszeichenfolgen "Integrated Security" verwendet werden. - - Credential kann nicht mit dem Schlüsselwort "Context Connection" verwendet werden. - AccessToken-Eigenschaft kann nicht festgelegt werden, wenn 'UserID', 'UID', 'Password' oder 'PWD' in der Verbindungszeichenfolge angegeben wurde. AccessToken-Eigenschaft kann nicht festgelegt werden, wenn das Schlüsselwort für Verbindungszeichenfolgen 'Integrated Security' auf 'true' oder 'SSPI' gesetzt wurde. - - AccessToken-Eigenschaft kann nicht mit dem Schlüsselwort 'Context Connection' festgelegt werden. - AccessToken-Eigenschaft kann nicht festgelegt werden, wenn bereits die Credential-Eigenschaft festgelegt wurde. @@ -1845,9 +1839,6 @@ Nicht konvertierbarer Typenkonflikt zwischen SourceColumn '{0}' von {1} und DataColumn '{2}' von {3}. - - DataColumn '{0}' für SourceColumn '{2}' ist nicht vorhanden. - DataColumn '{0}' in der DataTable '{1}' für SourceColumn '{2}' ist nicht vorhanden. @@ -2076,9 +2067,6 @@ Ungültiger Zielpufferoffset (Größe von {0}): {1} - - Keine Daten für die Zeile/Spalte. - Der numerische Wert ist zu groß, um in eine Dezimalzahl mit 96 Bits zu passen. @@ -2532,6 +2520,9 @@ Dieser Befehl erfordert eine asynchrone Verbindung. Legen Sie in der Verbindungszeichenfolge "Asynchronous Processing=true" fest. + + Timeout beim Versuch, eine Verbindung herzustellen. + Timeout abgelaufen. Die Verbindung wurde aus diesem Grund unterbrochen. @@ -2781,30 +2772,6 @@ Datenlänge '{0}' ist kleiner als 0. - - Ungültiger Versuch des Aufrufs von Methode {0}, wenn SqlResultSet geschlossen ist. - - - Operation kann nicht abgeschlossen werden, da SqlResultSet geschlossen ist. - - - '{0}' kann nicht aufgerufen werden, wenn der Datensatz schreibgeschützt ist. - - - Operation kann nicht abgeschlossen werden, da der Datensatz schreibgeschützt ist. - - - Ungültiger Versuch des Aufrufs von Methode {0}, wenn die aktuelle Zeile gelöscht wurde - - - Operation kann nicht abgeschlossen werden, da die aktuelle Zeile gelöscht wurde - - - Operation kann nicht abgeschlossen werden, da der Befehl, der SqlResultSet erstellt hat, von der ursprünglichen Verbindung getrennt wurde. SqlResultSet ist geschlossen. - - - SqlResultSet konnte für die angegebene Abfrage nicht mit den gewünschten Optionen erstellt werden. - '{0}' kann nicht aufgerufen werden, wenn SqlDataRecord schreibgeschützt ist. @@ -2874,51 +2841,21 @@ Die aktuell geladene System.Transactions.dll unterstützt keine globalen Transaktionen. Bitte aktualisieren Sie auf .NET Framework 4.6.2 oder höher. - - Batchupdates werden bei der Kontextverbindung nicht unterstützt. - - - Das einzige zusätzliche Schlüsselwort für die Verbindungszeichenfolge, das beim Anfordern der Kontextverbindung verwendet werden darf, lautet 'Type System Version'. - - - Die Kontextverbindung unterstützt nicht Type System Version=SQL Server 2000. - Das Herstellen einer Verbindung mit der Kontextverbindung über Microsoft.Data.SqlClient wird nicht unterstützt. - - Die Kontextverbindung wird bereits verwendet. - - - Die angeforderte Operation erfordert einen SqlClr -Kontext, der nur bei Ausführung im Sql Server-Prozess verfügbar ist. - - - Für die angeforderte Operation ist ein SQL Server-Ausführungsthread erforderlich. Der aktuelle Thread wurde durch Benutzercode oder einen anderen Nicht-Sql Server-Modulcode gestartet. - Geschachtelte 'TransactionScopes' werden nicht unterstützt. - - Die angeforderte Operation ist für die Kontextverbindung nicht verfügbar. - - - Die Benachrichtigungen sind für die Kontextverbindung nicht verfügbar. - Unerwartetes Serverereignis: {0}. - - Benutzerinstanzen sind bei der Ausführung im Sql Server-Prozess nicht zulässig. - Die Länge von '{0}' muss mit der Länge von '{1}' übereinstimmen. Der 'SqlDbType' '{0}' ist für {1} unzulässig. Unterstützt wird nur {2}. - - Fehler bei einer vorherigen Zeile, die an die SqlPipe gesendet wurde. 'SendResultsEnd' muss aufgerufen werden, bevor eine andere Zeile gesendet werden kann. - Zu viele Werte. @@ -3666,9 +3603,6 @@ Dieses SqlCommand-Objekt ist bereits mit einem anderen SqlDependency-Objekt verknüpft. - - Das 'SqlDependency'-Objekt kann nicht erstellt werden, wenn es im Rahmen des SQL Server-Prozesses ausgeführt wird. - Der SQL Server Service Broker für die aktuelle Datenbank ist nicht aktiviert. Als Folge davon werden keine Abfragebenachrichtigungen unterstützt. Aktivieren Sie den Service Broker für diese Datenbank, wenn Sie Benachrichtigungen verwenden möchten. @@ -3729,21 +3663,6 @@ Es sind keine Datensätze in der 'SqlDataRecord'-Enumeration vorhanden. Verwenden Sie zum Senden eines table-valued parameters ohne Zeilen stattdessen einen null-Verweis für den Wert. - - 'SqlPipe' unterstützt nicht die Ausführung eines Befehls bei einer Nicht-Kontextverbindung. - - - Die Nachrichtenlänge {0} überschreitet die zulässige Länge von 4.000. - - - Die Pipe konnte nicht verwendet werden, da sie zurzeit eine andere Operation ausführt. - - - Momentan wird ein Resultset an die Pipe gesendet. Beenden Sie das aktuelle Resultset vor dem Aufruf von {0}. - - - Das Resultset wurde nicht initiiert. Rufen Sie 'SendResultSetStart' vor dem Aufruf von {0} auf. - HTTP-Anbieter @@ -3808,7 +3727,7 @@ Zuordnung des Anschlusses mit E/A-Abschlussmechanismus fehlgeschlagen - + Socket ist NULL. Timeoutfehler @@ -4446,18 +4365,6 @@ Definiert das Verhalten im Blockierungszeitraum für einen Verbindungspool. - - .database.windows.net - - - .database.cloudapi.de - - - .database.usgovcloudapi.net - - - .database.chinacloudapi.cn - Definiert, ob das Zwischenspeichern von Abfragemetadaten aktiviert ist. @@ -4746,4 +4653,31 @@ Ungültiger Versuch, „JsonDocument“ für Spalte „{0}“ abzurufen. „JsonDocument“ wird nur für Spalten vom Typ JSON unterstützt. + + Puffer wurde zu weit erweitert {0} + + + Maximale Puffergröße überschritten: {0} + + + DataColumn '{0}' für SourceColumn '{2}' ist nicht vorhanden. + + + Die SqlBatchCommand-Liste wurde nicht initialisiert. + + + Ungültiger Versuch, Vektordaten aus Spalte „{0}“ abzurufen. Vektoren werden nur für Spalten vom Typ Vektor unterstützt. + + + Nicht unterstützter Vektortyp „{0}“. + + + Der Nullwert wird für den Ausgabeparameter „{0}“ des SqlDbtype-Vektors nicht unterstützt. + + + Ungültiger Vektorheader empfangen. + + + {0} Ungültige JSON-Zeichenfolge für Vektor. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx index e6c92a632b..9b537b9e6c 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.es.resx @@ -231,18 +231,12 @@ No se pueden utilizar Credential con la palabra clave de cadena de conexión de Integrated Security. - - No se pueden utilizar Credential con la palabra clave de Context Connection. - No se puede establecer la propiedad AccessToken si en la cadena de conexión se han especificado "UserID", "UID", "Password" o "PWD". No se puede establecer la propiedad AccessToken si la palabra clave de la cadena de conexión "Integrated Security" se ha establecido en "true" o "SSPI". - - No se puede establecer la propiedad AccessToken con la palabra clave "Context Connection". - No se puede establecer la propiedad AccessToken si la propiedad Credential ya está establecida. @@ -1845,9 +1839,6 @@ Falta de coincidencia entre los tipos que no se pueden convertir SourceColumn '{0}' de {1} y DataColumn '{2}' de {3}. - - Falta DataColumn '{0}' para SourceColumn '{2}'. - Falta DataColumn '{0}' en DataTable '{1}' para SourceColumn '{2}'. @@ -2076,9 +2067,6 @@ Desplazamiento del búfer de destino (tamaño de {0}) no válido: {1} - - No hay ningún dato disponible para la fila o columna. - El valor numérico es demasiado grande para ajustarlo a un valor decimal de 96 bits. @@ -2532,6 +2520,9 @@ Este comando requiere una conexión asincrónica. Establezca "Asynchronous Processing=true" en la cadena de conexión. + + Se agotó el tiempo de espera del intento de conexión. + Valor de tiempo de espera caducado. Por ello, se ha interrumpido la conexión. @@ -2781,30 +2772,6 @@ La longitud de los datos '{0}' es menor que 0. - - Intento no válido de llamar al método {0} cuando SqlResultSet está cerrado. - - - No se puede completar la operación porque SqlResultSet está cerrado. - - - No se puede llamar a '{0}' cuando el registro es de solo lectura. - - - No se puede completar la operación porque el registro es de solo lectura. - - - Intento no válido de llamar al método {0} cuando se ha eliminado la fila actual - - - No se puede completar la operación porque se ha eliminado la fila actual - - - No se puede completar la operación porque el comando que creó SqlResultSet se ha disociado de la conexión original. SqlResultSet está cerrado. - - - No se puede crear SqlResultSet para la consulta proporcionada con las opciones deseadas. - No se puede llamar a '{0}' cuando SqlDataRecord es de solo lectura. @@ -2874,51 +2841,21 @@ El archivo System.Transactions.dll cargado actualmente no es compatible con las transacciones globales. Actualice a .NET Framework 4.6.2 o posterior. - - La conexión de contexto no admite la actualización de procesamiento por lotes. - - - La palabra clave Type System Version es la única palabra clave de cadena de conexión adicional que se puede emplear al solicitar la conexión de contexto. - - - La conexión de contexto no admite Type System Version=SQL Server 2000. - No se admite la conexión a la conexión de contexto mediante Microsoft.Data.SqlClient. - - La conexión de contexto ya está en uso. - - - La operación solicitada necesita un contexto SqlClr que sólo está disponible cuando se utiliza con el proceso de Sql Server. - - - La operación solicitada necesita un proceso de ejecución de Sql Server. El proceso de ejecución actual comenzó por un código de usuario u otro código de motor de servidor que no es Sql. - No se admiten las TransactionScopes anidadas. - - La operación solicitada no está disponible en la conexión de contexto. - - - Las notificaciones no están disponibles en la conexión de contexto. - Evento de servidor inesperado: {0}. - - No se permiten las instancias de usuario al funcionar dentro de proceso del Sql Server. - La longitud de '{0}' debe coincidir con la longitud de '{1}'. SqlDbType '{0}' no es válido para {1}. Sólo admite {2}. - - Error al enviar la fila anterior a SqlPipe. Se debe llamar a SendResultsEnd antes de poder enviar algo más. - Demasiados valores. @@ -3666,9 +3603,6 @@ Este objeto SqlCommand ya está asociado a otro objeto SqlDependency. - - No se puede crear el objeto SqlDependency al funcionar dentro del proceso de SQL Server. - No está habilitado SQL Server Service Broker para la base de datos actual y, en consecuencia, no se admiten las notificaciones de consulta. Habilite Service Broker en esta base de datos si desea utilizar las notificaciones. @@ -3729,21 +3663,6 @@ No hay registros en la enumeración SqlDataRecord. Para enviar un table-valued parameter sin filas, utilice en su lugar una referencia null para el valor. - - SqlPipe no admite ejecutar un comando con una conexión que no es de contexto. - - - La longitud del mensaje {0} excede la longitud máxima admitida de 4000. - - - No se puede usar la canalización mientras esté ocupado con otra operación. - - - Se está enviando un conjunto de resultados a la canalización. Finalice el conjunto de resultados actual antes de llamar {0}. - - - No se ha iniciado el conjunto de resultados. Llame a SendResultSetStart antes de llamar {0}. - Proveedor HTTP @@ -3808,7 +3727,7 @@ Error al completar el mecanismo de asociación de puerto con E/S - + El socket es null Error de tiempo de espera @@ -4446,18 +4365,6 @@ Define el comportamiento del período de bloqueo para un grupo de conexiones. - - .database.windows.net - - - .database.cloudapi.de - - - .database.usgovcloudapi.net - - - .database.chinacloudapi.cn - Define si está habilitado el almacenamiento en caché de los metadatos de consulta. @@ -4746,4 +4653,31 @@ Intento no válido de obtener JsonDocument en la columna '{0}'. JsonDocument solo se admite para columnas de tipo json. + + El búfer ha avanzado demasiado {0} + + + Se ha superado el tamaño máximo del búfer: {0} + + + Falta DataColumn '{0}' para SourceColumn '{2}'. + + + No se ha inicializado la lista SqlBatchCommand. + + + Intento no válido de obtener datos vectoriales de la columna '{0}'. Los vectores solo son compatibles con columnas del tipo vector. + + + El tipo de vector "{0}" no es compatible. + + + El valor 'null' no es compatible con el parámetro de salida '{0}' de SqlDbType.Vector. + + + Se recibió un encabezado de vector no válido. + + + Cadena JSON {0} no válida para el vector. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx index 23c2be98bd..108f0a1f0f 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.fr.resx @@ -231,18 +231,12 @@ Impossible d'utiliser de Credential avec le mot-clé de chaîne de connexion de Integrated Security. - - Impossible d'utiliser de Credential avec le mot clé de Context Connection. - Impossible de définir la propriété AccessToken si « UserID », « UID », « Password » ou « PWD » a été spécifié dans la chaîne de connexion. Impossible de définir la propriété AccessToken si le mot clé de chaîne de connexion « Integrated Security » a la valeur « true » ou « SSPI ». - - Impossible de définir la propriété AccessToken avec le mot clé « Context Connection ». - Impossible de définir la propriété AccessToken si la propriété Credential est déjà définie. @@ -1845,9 +1839,6 @@ Incompatibilité de type ne pouvant pas être converti entre SourceColumn '{0}' de {1} et DataColumn '{2}' de {3}. - - DataColumn '{0}' manquant pour SourceColumn '{2}'. - DataColumn '{0}' manquant dans DataTable '{1}' pour SourceColumn '{2}'. @@ -2076,9 +2067,6 @@ Mémoire tampon de destination non valide (taille de {0}) offset : {1} - - Aucune donnée n'existe pour la ligne/colonne. - La valeur numérique est trop élevée pour tenir dans un décimal à 96 bits. @@ -2532,6 +2520,9 @@ Cette commande requiert une connexion asynchrone. Définissez "Asynchronous Processing=true" dans la chaîne de connexion. + + La tentative de connexion a expiré. + Le délai d'attente est arrivé à expiration. Par conséquent, la connexion a été interrompue. @@ -2781,30 +2772,6 @@ La longueur des données « {0} » est inférieure à 0. - - Tentative d'appel de la méthode {0} non valide avec SqlResultSet fermé. - - - Opération impossible : le SqlResultSet est fermé. - - - '{0}' ne peut pas être appelé lorsque l'enregistrement est en lecture seule. - - - Opération impossible : l'enregistrement est en lecture seule. - - - Tentative d'appel de la méthode {0} non valide si la ligne actuelle est supprimée - - - Opération impossible : la ligne actuelle a été supprimée - - - Opération impossible : la commande qui a créé le SqlResultSet est dissociée de la connexion d'origine. Le SqlResultSet est fermé. - - - Impossible de créer le SqlResultSet pour la requête fournie avec les options souhaitées. - Impossible d'appeler '{0}' si le SqlDataRecord est en lecture seule. @@ -2874,51 +2841,21 @@ Le fichier System.Transactions.dll actuellement chargé ne prend pas en charge les transactions globales. Effectuez une mise à niveau vers .NET Framework 4.6.2 ou ultérieur. - - Le traitement par lots des mises à jour n'est pas pris en charge sur la connexion du contexte. - - - Le seul mot clé de chaîne de connexion supplémentaire qui peut être utilisé lors de la demande de la connexion du contexte est le mot clé Type System Version. - - - La connexion du contexte ne prend pas en charge Type System Version=SQL Server 2000. - La connexion à la connexion de contexte à l’aide de Microsoft.Data.SqlClient n’est pas prise en charge. - - La connexion du contexte est déjà en cours d'utilisation. - - - L'opération demandée nécessite un contexte SqlClr, qui n'est disponible qu'en cas d'exécution dans le processus Sql Server. - - - L'opération demandée nécessite un thread d'exécution Sql Server. Le thread en cours a été démarré par code d'utilisateur ou autre code de moteur non-Sql Server. - Les TransactionScopes imbriquées ne sont pas prises en charge. - - L'opération demandée n'est pas disponible sur la connexion du contexte. - - - Les notifications ne sont pas disponibles sur la connexion du contexte. - Événement serveur inattendu : {0}. - - Les instances d'utilisateur ne sont pas autorisées en cas d'exécution dans le processus Sql Server. - La longueur de '{0}' doit correspondre à celle de '{1}'. Le type SqlDbType '{0}' n'est pas valide pour {1}. Seul {2} est pris en charge. - - Une erreur s’est produite avec une ligne précédente envoyée au SqlPipe. SendResultsEnd doit être appelé avant que quoi que ce soit d’autre puisse être envoyé. - Trop de valeurs. @@ -3666,9 +3603,6 @@ Cet objet SqlCommand est déjà associé à un autre objet SqlDependency. - - L'objet SqlDependency ne peut pas être créé en cas d'exécution dans le processus SQL Server. - Le SQL Server Service Broker de la base de données actuelle n'est pas activé. Par conséquent, les notifications de requête ne sont pas prises en charge. Activez le Service Broker pour cette base de données si vous souhaitez utiliser des notifications. @@ -3729,21 +3663,6 @@ Il n'existe aucun enregistrement dans l'énumération SqlDataRecord. Pour envoyer un table-valued parameter sans ligne, utilisez plutôt une référence null pour la valeur. - - SqlPipe ne prend pas en charge l'exécution d'une commande avec une connexion qui n'est pas une connexion du contexte. - - - La longueur de message {0} dépasse la longueur maximale prise en charge de 4000. - - - Impossible d'utiliser le canal tant qu'il est occupé par une autre opération. - - - Une série de résultats est actuellement envoyée au canal. Terminez la série des résultats avant d'appeler {0}. - - - La série de résultats n'a pas été initialisée. Appelez SendResultSetStart avant d'appeler {0}. - Fournisseur HTTP @@ -3808,7 +3727,7 @@ Échec de l'association du port avec le mécanisme de terminaison d'E/S - + Le socket est nul Erreur de délai d'attente @@ -4446,18 +4365,6 @@ Définit le comportement de la période de blocage pour un pool de connexions. - - .database.windows.net - - - .database.cloudapi.de - - - .database.usgovcloudapi.net - - - .database.chinacloudapi.cn - Définit si la mise en cache des métadonnées de requête est activée. @@ -4746,4 +4653,31 @@ Tentative non valide d’obtention de JsonDocument sur la colonne « {0} ». JsonDocument est uniquement pris en charge pour les colonnes de type JSON. + + Mémoire tampon avancée trop loin {0} + + + Taille maximale de la mémoire tampon dépassée : {0} + + + DataColumn '{0}' manquant pour SourceColumn '{2}'. + + + La liste SqlBatchCommand n'a pas été initialisée. + + + Tentative non valide d’obtention de données vectorielles à partir de la colonne « {0} ». Les vecteurs ne sont pris en charge que pour les colonnes de type vecteur. + + + Type de vecteur « {0} » non pris en charge. + + + Valeur « null » non prise en charge pour le paramètre de sortie « {0} » du vecteur SqlDbtype. + + + En-tête de vecteur non valide reçu. + + + Chaîne JSON {0} non valide pour le vecteur. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx index 128a369191..d56314dceb 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.it.resx @@ -231,18 +231,12 @@ Impossibile utilizzare Credential con la parola chiave per la stringa di connessione Integrated Security. - - Impossibile utilizzare Credential con la parola chiave Context Connection. - Non è possibile impostare la proprietà AccessToken se nella stringa di connessione è stato specificato 'UserID', 'UID', 'Password' o 'PWD'. Non è possibile impostare la proprietà AccessToken se la parola chiave della stringa di connessione 'Integrated Security' è stata impostata su 'true' o 'SSPI'. - - Non è possibile impostare la proprietà AccessToken con la parola chiave 'Context Connection'. - Non è possibile impostare la proprietà AccessToken se la proprietà Credential è già impostata. @@ -1845,9 +1839,6 @@ Impossibile eseguire la conversione. Il tipo della proprietà SourceColumn "{0}" di {1} non corrisponde a quello della proprietà DataColumn "{2}" di {3}. - - DataColumn '{0}' mancante per la SourceColumn '{2}'. - DataColumn '{0}' mancante nella DataTable '{1}' per la SourceColumn '{2}'. @@ -2076,9 +2067,6 @@ Offset buffer di destinazione (dimensione {0}) non valido: {1} - - Nessun dato esistente per la riga/colonna. - Il valore numerico non è contenibile in un numero decimale a 96 bit. @@ -2532,6 +2520,9 @@ Il comando richiede una connessione asincrona. Impostare "Asynchronous Processing=true" nella stringa di connessione. + + Timeout del tentativo di connessione. + Timeout scaduto. La connessione è stata interrotta. @@ -2781,30 +2772,6 @@ Lunghezza dei dati '{0}' minore di 0. - - Tentativo non valido di chiamare il metodo {0} quando SqlResultSet è chiuso. - - - Impossibile completare l'operazione, perché SqlResultSet è chiuso. - - - Impossibile chiamare "{0}" se il record è di sola lettura. - - - Impossibile completare l'operazione. Il record è di sola lettura. - - - Tentativo non valido di chiamare il metodo {0} quando la riga corrente è stata eliminata - - - Impossibile completare l'operazione perché la riga corrente è stata eliminata - - - Impossibile completare l'operazione. Il comando che ha creato SqlResultSet è stato dissociato dalla connessione originale. SqlResultSet è chiuso. - - - Impossibile creare SqlResultSet con le opzioni desiderate per la query specificata. - Impossibile chiamare "{0}" quando SqlDataRecord è di sola lettura. @@ -2874,51 +2841,21 @@ Il file System.Transactions.dll attualmente caricato non supporta le transazioni globali. Eseguire l'aggiornamento a .NET Framework 4.6.2 o versione successiva. - - L'esecuzione di batch di aggiornamenti non è supportata nella connessione contesto. - - - L'unica parola chiave per la stringa di connessione supplementare che può essere utilizzata quando si richiede la connessione contesto è Type System Version. - - - La connessione contesto non supporta Type System Version=SQL Server 2000. - La connessione alla connessione di contesto tramite Microsoft.Data.SqlClient non è supportata. - - La connessione contesto è giù in uso. - - - L'operazione richiesta richiede un contesto SqlClr che è disponibile solo quando si opera nel processo Sql Server. - - - L'operazione richiesta richiede un thread di esecuzione Sql Server Il thread corrente è stato avviato da un codice utente o da un altro codice motore diverso da Sql Server. - Non sono supportati TransactionScopes annidati. - - L'operazione richiesta non è disponibile nella connessione contesto. - - - Le notifiche non sono disponibili nella connessione contesto. - Evento server imprevisto: {0}. - - Le istanze utente non sono consentite quando è in funzione il processo Sql Server. - La lunghezza di '{0}' deve corrispondere alla lunghezza di '{1}'. SqlDbType '{0}' non è valido per {1}. Solo {2} è supportato. - - Si è verificato un errore con una riga precedente inviata alla SqlPipe. Prima di poter eseguire altri invii, è necessario chiamare SendResultsEnd. - Troppi valori. @@ -3666,9 +3603,6 @@ L'oggetto SqlCommand è già associato a un altro oggetto SqlDependency. - - Impossibile creare un oggetto SqlDependency durante l'esecuzione nel processo SQL Server. - SQL Server Service Broker non è attivato per il database corrente, pertanto non sono supportate le notifiche di query. Attivare il servizio Service Broker per questo database se si desidera utilizzare le notifiche. @@ -3729,21 +3663,6 @@ Non sono disponibili record nell'enumerazione SqlDataRecord. Utilizzare un riferimento con valore null, per inviare un table-valued parameter senza righe. - - SqlPipe non supporta l'esecuzione di un comando con una connessione diversa da una connessione contesto. - - - La lunghezza del messaggio {0} supera la lunghezza massima supportata di 4000. - - - Impossibile utilizzare la pipe se è occupata con un'altra operazione. - - - Si sta inviando alla pipe un set di risultati. Terminare il set di risultati corrente prima di chiamare {0}. - - - Il set di risultati non è stato iniziato. Chiamare SendResultSetStart prima di chiamare {0}. - Provider HTTP @@ -3808,7 +3727,7 @@ Associazione della porta al meccanismo di completamento I/O non riuscita - + Il socket è null Errore di timeout @@ -4446,18 +4365,6 @@ Consente di definire il comportamento del periodo di blocco per un pool di connessioni. - - .database.windows.net - - - .database.cloudapi.de - - - .database.usgovcloudapi.net - - - .database.chinacloudapi.cn - Consente di definire se la memorizzazione nella cache dei metadati della query è abilitata. @@ -4746,4 +4653,31 @@ Tentativo non valido di ottenere JsonDocument nella colonna '{0}'. JsonDocument è supportato solo per colonne di tipo JSON. + + Buffer avanzato troppo {0} + + + Superata la dimensione massima del buffer: {0} + + + DataColumn '{0}' mancante per la SourceColumn '{2}'. + + + L'elenco SqlBatchCommand non è stato inizializzato. + + + Tentativo non valido di ottenere dati vettoriali dalla colonna '{0}'. I vettori sono supportati solo per le colonne di tipo vettore. + + + Tipo di vettore '{0}' non supportato. + + + Il valore 'null' non è supportato per il parametro di output '{0}' di SqlDbtype Vector. + + + Intestazione del vettore non valida ricevuta. + + + {0} Stringa JSON non valida per il vettore. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx index c8e55f5ab3..311e026244 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ja.resx @@ -231,18 +231,12 @@ Integrated Security 接続文字列キーワードを含む Credential は使用できません。 - - Context Connection キーワードを含む Credential は使用できません。 - 接続文字列で 'UserID'、'UID'、'Password'、または 'PWD' が指定されている場合は、AccessToken プロパティを設定できません。 'Integrated Security' 接続文字列キーワードが 'true' または 'SSPI' に設定されている場合は、AccessToken プロパティを設定できません。 - - 'Context Connection' キーワードを含む AccessToken プロパティは設定できません。 - Credential プロパティが既に設定されている場合は、AccessToken プロパティを設定できません。 @@ -1845,9 +1839,6 @@ SourceColumn '{1}' の '{0}' と {3} の DataColumn '{2}' の間に変換不可能な型の不一致があります。 - - SourceColumn '{2}' の DataColumn '{0}' が見つかりません。 - SourceColumn '{2}' の DataTable '{1}' に DataColumn '{0}' が見つかりません。 @@ -2076,9 +2067,6 @@ ターゲット バッファー (サイズ {0}) オフセット : {1} が無効です。 - - 行および列にデータが存在しません。 - この数値は 10 進数の 96 ビットに適合させるには大きすぎます。 @@ -2532,6 +2520,9 @@ このコマンドには非同期接続が必要です。接続文字列に "Asynchronous Processing=true" を設定してください。 + + 接続試行がタイムアウトしました。 + タイムアウトに達しました。その結果、接続が切断されました。 @@ -2781,30 +2772,6 @@ データの長さ '{0}' が 0 未満です。 - - SqlResultSet が閉じている場合は、メソッド {0} の呼び出しは無効です。 - - - SqlResultSet が閉じているため、操作を完了できません。 - - - レコードが読み取り専用の場合は、'{0}' を呼び出すことはできません。 - - - レコードが読み取り専用のため、操作を完了できません。 - - - 現在の行が削除されている場合は、メソッド {0} の呼び出しは無効です。 - - - 現在の行が削除されているため、操作を完了できません。 - - - SqlResultSet を作成したコマンドが元の接続から切断されているため、操作を完了できません。SqlResultSet は閉じています。 - - - 要求されたオプションを使用して指定されたクエリの SqlResultSet を作成できません。 - SqlDataRecord が読み取り専用の場合は、'{0}' を呼び出すことはできません。 @@ -2874,51 +2841,21 @@ 現在読み込まれている System.Transactions.dll では、グローバル トランザクションはサポートされていません。.NET Framework 4.6.2 またはそれ以降にアップグレードしてください。 - - コンテキスト接続では、更新のバッチ処理はサポートされていません。 - - - コンテキスト接続を要求する際に、この他に使用される唯一の接続文字列キーワードは Type System Version キーワードです。 - - - コンテキスト接続は Type System Version=SQL Server 2000 をサポートしていません。 - Microsoft.Data.SqlClient を使用したコンテキスト接続への接続はサポートされていません。 - - コンテキスト接続は既に使用されています。 - - - 要求された操作には、SqlClr コンテキストが必要です。このコンテキストは、Sql Server プロセス内で実行する場合にのみ使用できます。 - - - 要求された操作には、Sql Server の実行スレッドが必要です。現在のスレッドはユーザー コードまたはその他の non-Sql Server エンジン コードによって開始されました。 - TransactionScopes を入れ子にすることはできません。 - - 要求された操作をコンテキスト接続で行うことはできません。 - - - コンテキスト接続で通知を使用することはできません。 - サーバーの予期しないイベント : {0}。 - - Sql Server プロセスで実行している場合は、ユーザー インスタンスは許可されません。 - '{0}' と '{1}' の長さは同じである必要があります。 {1} に対し、SqlDbType '{0}' は無効です。サポートしているのは {2} のみです。 - - SqlPipe に送信された前の行でエラーが発生しました。他のものを送信する前に SendResultsEnd を呼び出す必要があります。 - 値が多すぎます。 @@ -3666,9 +3603,6 @@ この SqlCommand オブジェクトは、既に別の SqlDependency オブジェクトに関連付けられています。 - - SQL Server プロセス内で実行されている場合、SqlDependency オブジェクトは作成できません。 - 現在のデータベースの SQL Server Service Broker は有効になっていません。このため、クエリ通知はサポートされません。通知を利用する場合は、このデータベースの Service Broker を有効にしてください。 @@ -3729,21 +3663,6 @@ SqlDataRecord 列挙にレコードがありません。行のない table-valued parameter を送信したい場合は、代わりにその値の null 参照を使用します。 - - SqlPipe では、コンテキスト接続ではない接続を使用してコマンドを実行することはできません。 - - - メッセージの長さ {0} が、サポートしている最大長である 4000 を超えています。 - - - 別の操作でパイプがビジー状態である場合、このパイプを使用できない可能性があります。 - - - 結果セットをパイプに送信しています。{0} を呼び出す前に現在の結果セットを終了してください。 - - - 結果セットが初期化されていません。{0} を呼び出す前に SendResultSetStart を呼び出してください。 - HTTP プロバイダー @@ -3808,7 +3727,7 @@ ポートと I/O 完了メカニズムの関連付けに失敗しました - + ソケットは null です タイムアウト エラー @@ -4446,18 +4365,6 @@ 接続プールのためのブロック期間ビヘイビアーを定義します。 - - .database.windows.net - - - .database.cloudapi.de - - - .database.usgovcloudapi.net - - - .database.chinacloudapi.cn - クエリ メタデータ キャッシュが有効であるかどうかを定義します。 @@ -4746,4 +4653,31 @@ 列 '{0}' で JsonDocument を取得しようとしましたが無効です。JsonDocument は json 型の列でのみサポートされています。 + + バッファーの進行が過剰です: {0} + + + バッファーの最大サイズを超えました: {0} + + + SourceColumn '{2}' の DataColumn '{0}' が見つかりません。 + + + SqlBatchCommand リストが初期化されていません。 + + + 列 '{0}' からベクター データを取得しようとしましたが無効です。ベクターは、ベクター型の列でのみサポートされます。 + + + サポートされていないベクター型 '{0}'。 + + + SqlDbtype ベクターの出力パラメーター '{0}' では、'null' 値はサポートされていません。 + + + 無効なベクター ヘッダーを受信しました。 + + + {0} ベクターの JSON 文字列が無効です。 + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx index 6692779191..8ed8543b04 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ko.resx @@ -231,18 +231,12 @@ Integrated Security 연결 문자열 키워드가 포함된 Credential을 사용할 수 없습니다. - - Context Connection 키워드가 포함된 Credential을 사용할 수 없습니다. - 연결 문자열에서 'UserID', 'UID', 'Password' 또는 'PWD'를 지정한 경우 AccessToken 속성을 설정할 수 없습니다. 'Integrated Security' 연결 문자열 키워드가 'true' 또는 'SSPI'로 설정된 경우 AccessToken 속성을 설정할 수 없습니다. - - 'Context Connection' 키워드를 사용하여 AccessToken 속성을 지정할 수 없습니다. - Credential 속성이 이미 설정된 경우 AccessToken 속성을 설정할 수 없습니다. @@ -1845,9 +1839,6 @@ {1}의 SourceColumn '{0}'과(와) {3}의 DataColumn '{2}'의 유형이 일치하지 않습니다. - - SourceColumn '{2}'에 DataColumn '{0}'이(가) 없습니다. - SourceColumn '{2}'의 DataTable '{1}'에 DataColumn '{0}'이(가) 없습니다. @@ -2076,9 +2067,6 @@ 대상 버퍼(크기: {0}) 오프셋이 잘못되었습니다. {1} - - 행/열에 대한 데이터가 없습니다. - 숫자 값이 너무 커서 96비트 10진수에 맞지 않습니다. @@ -2532,6 +2520,9 @@ 이 명령을 사용하려면 비동기 연결이 필요합니다. 연결 문자열에서 "Asynchronous Processing=true"로 설정하십시오. + + 연결 시도 시간이 초과되었습니다. + 제한 시간이 만료되었으므로 연결이 끊겼습니다. @@ -2781,30 +2772,6 @@ 데이터 길이 '{0}'은(는) 0보다 작습니다. - - SqlResultSet이 닫혀 있으면 메서드 {0}을(를) 호출할 수 없습니다. - - - SqlResultSet이 닫혀 있으므로 작업을 완료할 수 없습니다. - - - 레코드가 읽기 전용이면 '{0}'을(를) 호출할 수 없습니다. - - - 레코드가 읽기 전용이므로 작업을 완료할 수 없습니다. - - - 현재 행이 삭제되면 메서드 {0}을(를) 호출할 수 없습니다. - - - 현재 행이 삭제되었으므로 작업을 완료할 수 없습니다. - - - SqlResultSet을 만든 명령이 원래 연결에서 분리되었기 때문에 작업을 완료할 수 없습니다. SqlResultSet이 닫혀 있습니다. - - - 지정된 쿼리에 대한 SqlResultSet을 원하는 옵션으로 만들 수 없습니다. - SqlDataRecord가 읽기 전용이면 '{0}'을(를) 호출할 수 없습니다. @@ -2874,51 +2841,21 @@ 현재 로드된 System.Transactions.dll은 전역 트랜잭션을 지원하지 않습니다. .NET Framework 4.6.2 이상으로 업그레이드하세요. - - 컨텍스트 연결에서는 일괄 처리 업데이트가 지원되지 않습니다. - - - 컨텍스트 연결을 요청할 때 유일하게 추가할 수 있는 연결 문자열 키워드는 Type System Version 키워드입니다. - - - 컨텍스트 연결은 Type System Version=SQL Server 2000을 지원하지 않습니다. - Microsoft.Data.SqlClient를 사용하여 컨텍스트 연결에 연결할 수 없습니다. - - 컨텍스트 연결이 이미 사용 중입니다. - - - 요청한 작업을 수행하려면 Sql Server 프로세스에서 실행 중인 경우에만 사용할 수 있는 SqlClr 컨텍스트가 필요합니다. - - - 요청한 작업을 수행하려면 Sql Server 실행 스레드가 필요합니다. 현재 스레드는 사용자 코드 또는 Sql Server가 아닌 다른 엔진 코드에서 시작했습니다. - 중첩 TransactionScopes는 지원되지 않습니다. - - 요청한 작업은 컨텍스트 연결에서 사용할 수 없습니다. - - - 알림은 컨텍스트 연결에서 사용할 수 없습니다. - 예기치 않은 서버 이벤트가 발생했습니다. {0} - - Sql Server 프로세스에서 실행할 때는 사용자 인스턴스가 허용되지 않습니다. - '{0}'의 길이는 '{1}'의 길이와 일치해야 합니다. SqlDbType '{0}'은(는) {1}에 사용할 수 없습니다. {2}만 지원됩니다. - - SqlPipe로 보낸 이전 행에서 오류가 발생했습니다. 다른 행을 보내려면 SendResultsEnd를 호출해야 합니다. - 값이 너무 많습니다. @@ -3666,9 +3603,6 @@ 이 SqlCommand 개체는 이미 다른 SqlDependency 개체에 연결되어 있습니다. - - SQL Server 프로세스 내에서 실행할 때는 SqlDependency 개체를 만들 수 없습니다. - 현재 데이터베이스에 대해 SQL Server Service Broker가 설정되어 있지 않으므로 쿼리 알림을 사용할 수 없습니다. 알림을 사용하려면 이 데이터베이스에 대해 Service Broker를 설정하십시오. @@ -3729,21 +3663,6 @@ SqlDataRecord 열거형에 레코드가 없습니다. 행이 없는 table-valued parameter를 보내려면 대신 값에 null 참조를 사용하십시오. - - SqlPipe는 컨텍스트 연결이 아닌 다른 연결 상태에서 명령을 실행할 수 없습니다. - - - 메시지 길이 {0}이(가) 지원되는 최대 길이 4000자를 초과했습니다. - - - 다른 작업을 수행 중인 동안에는 파이프를 사용할 수 없습니다. - - - 현재 결과 집합을 파이프로 보내고 있습니다. {0}을(를) 호출하기 전에 현재 결과 집합을 종료하십시오. - - - 결과 집합이 시작되지 않았습니다. {0}을(를) 호출하기 전에 SendResultSetStart를 호출하십시오. - HTTP 공급자 @@ -3808,7 +3727,7 @@ 포트와 입/출력 완료 메커니즘의 연결이 실패했습니다. - + 소켓이 null입니다. 시간 초과 오류입니다. @@ -4446,18 +4365,6 @@ 연결 풀에 대한 차단 기간 동작을 정의합니다. - - .database.windows.net - - - .database.cloudapi.de - - - .database.usgovcloudapi.net - - - .database.chinacloudapi.cn - 쿼리 메타데이터 캐싱을 사용하는지를 정의합니다. @@ -4746,4 +4653,31 @@ 열 '{0}'의 JsonDocument를 가져오려는 시도가 잘못되었습니다. JsonDocument는 json 형식의 열에 대해서만 지원됩니다. + + 버퍼가 너무 멉니다. {0} + + + 버퍼 최대 크기 초과: {0} + + + SourceColumn '{2}'에 DataColumn '{0}'이(가) 없습니다. + + + SqlBatchCommand 목록이 초기화되지 않았습니다. + + + 열 '{0}'에서 벡터 데이터를 가져오려는 시도가 잘못되었습니다. 벡터는 벡터 형식의 열에서만 지원됩니다. + + + 지원되지 않는 벡터 형식 '{0}'입니다. + + + SqlDbType Vector의 출력 매개변수 '{0}'에는 'null' 값이 지원되지 않습니다. + + + 잘못된 벡터 헤더가 수신되었습니다. + + + {0} 벡터에 대한 JSON 문자열이 잘못되었습니다. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx index 36ba315c21..fd8e76afb9 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pl.resx @@ -231,18 +231,12 @@ Nie można użyć elementu Credential ze słowem kluczowym parametrów połączenia Integrated Security. - - Nie można użyć elementu Credential ze słowem kluczowym Context Connection. - Nie można ustawić właściwości AccessToken, jeśli w parametrach połączenia określono element UserID, UID, Password lub PWD. Nie można ustawić właściwości AccessToken, jeśli dla słowa kluczowego „Integrated Security” w parametrach połączenia ustawiono wartość true lub SSPI. - - Nie można ustawić właściwości AccessToken ze słowem kluczowym „Context Connection”. - Nie można ustawić właściwości AccessToken, jeśli ustawiono już właściwość Credential. @@ -1845,9 +1839,6 @@ Niezgodność typów niemożliwych do konwersji między elementem SourceColumn {0} z {1} a elementem DataColumn {2} z {3}. - - Brak elementu DataColumn {0} dla elementu SourceColumn {2}. - Brak elementu DataColumn {0} w elemencie DataTable {1} dla elementu SourceColumn {2}. @@ -2076,9 +2067,6 @@ Nieprawidłowe przesunięcie buforu docelowego (rozmiar {0}): {1} - - Nie istnieją dane dla wiersza/kolumny. - Wartość numeryczna jest za duża – nie mieści się w 96-bitowej zmiennej dziesiętnej. @@ -2532,6 +2520,9 @@ To polecenie wymaga połączenia asynchronicznego. Ustaw wartość „Asynchronous Processing=true” w parametrach połączenia. + + Upłynął limit czasu próby połączenia. + Upłynął limit czasu. W wyniku tego połączenie zostało przerwane. @@ -2781,30 +2772,6 @@ Długość danych „{0}” ma wartość mniejszą niż 0. - - Nieprawidłowa próba wywołania metody {0} podjęta, gdy element SqlResultSet jest zamknięty. - - - Nie można ukończyć operacji, ponieważ element SqlResultSet jest zamknięty. - - - Nie można wywołać elementu „{0}”, jeśli rekord jest tylko do odczytu. - - - Nie można ukończyć operacji, ponieważ rekord jest tylko do odczytu. - - - Nieprawidłowa próba wywołania {0} podjęta, gdy bieżący wiersz został usunięty - - - Nie można ukończyć operacji, ponieważ bieżący wiersz został usunięty - - - Nie można ukończyć operacji, ponieważ polecenie, przy użyciu którego został utworzony element SqlResultSet, nie ma już skojarzenia z oryginalnym połączeniem. Element SqlResultSet jest zamknięty. - - - Nie można utworzyć elementu SqlResultSet dla podanego zapytania z żądanymi opcjami. - Nie można wywołać elementu {0}, jeśli element SqlDataRecord jest tylko do odczytu. @@ -2874,51 +2841,21 @@ Aktualnie załadowany System.Transactions.dll nie obsługuje transakcji globalnych. Uaktualnij do programu .NET Framework 4.6.2 lub nowszego. - - Przetwarzanie wsadowe aktualizacji nie jest obsługiwane dla połączenia kontekstu. - - - Jedynym dodatkowym słowem kluczowym parametrach połączenia, które może być używane podczas żądania połączenia kontekstu, jest słowo kluczowe dotyczące wersji systemu (Type System Version). - - - Połączenie kontekstu nie obsługuje wersji systemu Type System Version=SQL Server 2000. - Nawiązywanie połączenia kontekstowego przy użyciu Microsoft.Data.SqlClient nie jest obsługiwane. - - Połączenie kontekstu jest już używane. - - - Żądana operacja wymaga kontekstu SqlClr, który jest dostępny tylko podczas uruchamiania procesu programu Sql Server. - - - Żądana operacja wymaga wątku wykonywania programu Sql Server. Bieżący wątek został uruchomiony przez kod użytkownika lub inny kod aparatu programu innego niż Sql Server. - Zagnieżdżone elementy TransactionScopes nie są obsługiwane. - - Żądana operacja jest niedostępna dla połączenia kontekstu. - - - Powiadomienia nie są dostępne dla połączenia kontekstu. - Nieoczekiwane zdarzenie serwera: {0}. - - Wystąpienia użytkownika nie są dozwolone podczas uruchomienia procesu serwera Sql Server. - Długość {0} musi być zgodna z długością {1}. Element SqlDbType „{0}” jest nieprawidłowy dla {1}. Obsługiwany jest tylko element {2}. - - Wystąpił błąd z poprzednim wierszem wysłanym do elementu SqlPipe. Aby można było wysłać dowolne inne elementy, należy wywołać funkcję SendResultsEnd. - Zbyt wiele wartości. @@ -3666,9 +3603,6 @@ Ten obiekt SqlCommand został już skojarzony z innym obiektem SqlDependency. - - Obiektu SqlDependency nie można utworzyć podczas uruchomienia wewnątrz procesu programu SQL Server. - Usługa Microsoft SQL Server Service Broker dla bieżącej bazy danych nie jest włączony i w wyniku tego powiadomienia o zapytaniach nie są obsługiwane. Włącz usługę Service Broker dla tej bazy danych, jeśli chcesz używać powiadomień. @@ -3729,21 +3663,6 @@ Brak rekordów w wyliczeniu SqlDataRecord. Aby wysłać parametr z wartościami przechowywanymi w tabeli bez wierszy, użyj odwołania o wartości null. - - Element SqlPipe nie obsługuje wykonywania polecenia dla połączenia, które nie jest połączeniem kontekstu. - - - Długość komunikatu {0} przekracza maksymalną obsługiwaną długość wynoszącą 4000. - - - Nie można użyć potoku, gdy jest zajęty inną operacją. - - - Zestaw wyników jest obecnie wysyłany do potoku. Zakończ bieżący zestaw wyników przed wywołaniem {0}. - - - Zestaw wyników nie został zainicjowany. Wywołaj metodę SendResultSetStart przed wywołaniem {0}. - Dostawca HTTP @@ -3808,7 +3727,7 @@ Tworzenie skojarzenia portu z mechanizmem zakończenia We/Wy I/O nie powiodło się - + Gniazdo ma wartość null Błąd limitu czasu @@ -4446,18 +4365,6 @@ Definiuje zachowanie okresu blokowania dla puli połączeń. - - .database.windows.net - - - .database.cloudapi.de - - - .database.usgovcloudapi.net - - - .database.chinacloudapi.cn - Określa, czy jest włączone buforowanie metadanych zapytań. @@ -4746,4 +4653,31 @@ Nieprawidłowa próba pobrania pliku JsonDocument w kolumnie „{0}”. Obiekt JsonDocument jest obsługiwany tylko w przypadku kolumn typu JSON. + + Bufor przesunięty za daleko {0} + + + Przekroczono maksymalny rozmiar buforu: {0} + + + Brak elementu DataColumn {0} dla elementu SourceColumn {2}. + + + Lista SqlBatchCommand nie została zainicjowana. + + + Nieprawidłowa próba pobrania danych wektorowych z kolumny „{0}”. Wektory są obsługiwane tylko dla kolumn wektora typu. + + + Nieobsługiwany typ wektora „{0}”. + + + Wartość „null” nie jest obsługiwana dla parametru wyjściowego „{0}” wektora SqlDbtype. + + + Odebrano nieprawidłowy nagłówek wektora. + + + {0} Nieprawidłowy ciąg JSON wektora. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx index 1b42cec65c..03c8087d35 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.pt-BR.resx @@ -231,18 +231,12 @@ Não é possível usar a Credential com palavra-chave de cadeia de conexão de Integrated Security. - - Não é possível usar a Credential com palavra-chave de Context Connection. - Não é possível definir a propriedade AccessToken se 'UserID', 'UID', 'Password' ou 'PWD' tiver sido especificado na cadeia de conexão. Não é possível definir a propriedade AccessToken se a palavra-chave da cadeia de conexão 'Integrated Security' estiver sido definida para 'true' ou 'SSPI'. - - Não é possível definir a propriedade AccessToken com a palavra-chave 'Context Connection'. - Não é possível definir a propriedade AccessToken se a propriedade Credential já estiver definida. @@ -1845,9 +1839,6 @@ Tipos não conversíveis incompatíveis entre SourceColumn '{0}' de {1} e DataColumn '{2}' de {3}. - - DataColumn '{0}' de SourceColumn '{2}' inexistente. - DataColumn '{0}' em DataTable '{1}' de SourceColumn '{2}' inexistente. @@ -2076,9 +2067,6 @@ Deslocamento de buffer de destino inválido (tamanho de {0}): {1} - - Não existem dados para a linha ou coluna. - Valor numérico muito grande para um decimal de 96 bits. @@ -2532,6 +2520,9 @@ Este comando requer uma conexão assíncrona. Defina "Asynchronous Processing=true" na cadeia de conexão. + + A tentativa de conexão atingiu o tempo limite. + Tempo limite expirado. Como resultado a conexão foi quebrada. @@ -2781,30 +2772,6 @@ O comprimento de dados '{0}' é menor que 0. - - Tentativa inválida para chamar o método {0} quando o SqlResultSet está fechado. - - - A operação não pode ser concluída, pois o SqlResultSet está fechado. - - - '{0}' não pode ser chamado quando o registro é somente leitura. - - - A operação não pode ser concluída, pois o registro é somente leitura. - - - Tentativa inválida para chamar o método {0} quando a linha atual foi excluída - - - A operação não foi concluída, pois a linha atual foi excluída - - - A operação não pode ser concluída, pois o comando que criou o SqlResultSet foi dissociado da conexão original. O SqlResultSet está fechado. - - - O SqlResultSet não pôde ser criado para a consulta fornecida com as opções desejadas. - '{0}' não pode ser chamado quando o SqlDataRecord é somente leitura. @@ -2874,51 +2841,21 @@ O System.Transactions.dll carregado no momento não dá suporte a Transações Globais. Atualize para o .NET Framework 4.6.2 ou posterior. - - Não há suporte para as atualizações em lote na conexão de contexto. - - - A única palavra-chave da cadeia de conexão adicional que deve ser usada ao solicitar a conexão de contexto é a palavra-chave Type System Version. - - - Não há suporte para a conexão de contexto Type System Version=SQL Server 2000. - Não há suporte para a conexão de contexto usando Microsoft.Data.SqlClient. - - A conexão de contexto já está em uso. - - - A operação solicitada requer um contexto SqlClr que somente está disponível durante a execução no processo Sql Server. - - - A operação solicitada requer um thread de execução Sql Server. O thread atual foi iniciado pelo código do usuário ou outro código de mecanismo sem ser o Sql Server. - Não há suporte para TransactionScopes aninhados. - - A operação solicitada não está disponível na conexão de contexto. - - - Notificações não estão disponíveis na conexão de contexto. - Evento de servidor inesperado: {0}. - - Instâncias do usuário não são permitidas durante a execução no processo Sql Server. - O comprimento de '{0}' deve corresponder ao comprimento de '{1}'. A SqlDbType '{0}' é inválida para {1}. Há suporte apenas para {2}. - - Ocorreu um erro com uma linha anteriormente enviada para o SqlPipe. O SendResultsEnd deve ser chamado antes de qualquer outro envio. - Excesso de valores. @@ -3666,9 +3603,6 @@ Esse objeto SqlCommand já está associado a outro objeto SqlDependency. - - Não é possível criar o objeto SqlDependency ao executar dentro do processo do SQL Server. - O SQL Server Service Broker do banco de dados atual não está habilitado e, como resultado, não há suporte a notificações de consultas. Habilite o Service Broker para este banco de dados se desejar usar notificações. @@ -3729,21 +3663,6 @@ Não há registros na enumeração SqlDataRecord. Para enviar um table-valued parameter sem linhas, use uma referência null para o valor. - - O SqlPipe não oferece suporte à execução de um comando com uma conexão que não seja conexão de contexto. - - - {0} ultrapassa o tamanho máximo de mensagens aceito de 4.000. - - - Não é possível usar o pipe enquanto ele está ocupado com outra operação. - - - Um conjunto de resultados está sendo enviado para o pipe. Encerre o conjunto de resultados atual antes de chamar {0}. - - - O conjunto de resultados não foi iniciado. Chame SendResultSetStart antes de chamar {0}. - Provedor de HTTP @@ -3808,7 +3727,7 @@ Falha ao associar porta e mecanismo de conclusão de E/S - + O soquete é nulo Erro de tempo limite @@ -4446,18 +4365,6 @@ Define o comportamento de período de bloqueio para um pool de conexões. - - .database.windows.net - - - .database.cloudapi.de - - - .database.usgovcloudapi.net - - - .database.chinacloudapi.cn - Define se o cache de metadados de consulta está habilitado. @@ -4746,4 +4653,31 @@ Tentativa inválida de obter JsonDocument na coluna ''{0}''. Só há suporte para JsonDocument em colunas do tipo json. + + O armazenamento em buffer avançou demais {0} + + + Tamanho máximo do buffer excedido: {0} + + + DataColumn '{0}' de SourceColumn '{2}' inexistente. + + + A lista SqlBatchCommand não foi inicializada. + + + Tentativa inválida de obter dados vetoriais da coluna '{0}'. Vetores são suportados apenas para colunas do tipo vetor. + + + Tipo de vetor não suportado '{0}'. + + + Valor 'null' não suportado para o parâmetro de saída '{0}' do tipo SqlDbType Vector. + + + Cabeçalho de vetor inválido recebido. + + + {0} Cadeia de caracteres JSON inválida para vetor. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx index f7947e8975..71cc24cc56 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.resx @@ -1845,9 +1845,6 @@ Inconvertible type mismatch between SourceColumn '{0}' of {1} and the DataColumn '{2}' of {3}. - - Missing the DataColumn '{0}' for the SourceColumn '{2}'. - Missing the DataColumn '{0}' in the DataTable '{1}' for the SourceColumn '{2}'. @@ -2532,6 +2529,9 @@ This command requires an asynchronous connection. Set "Asynchronous Processing=true" in the connection string. + + The connection attempt timed out. + Timeout expired. The connection has been broken as a result. @@ -3808,7 +3808,7 @@ Associating port with I/O completion mechanism failed - + Socket is null Timeout error @@ -4446,18 +4446,6 @@ Defines the blocking period behavior for a connection pool. - - .database.windows.net - - - .database.cloudapi.de - - - .database.usgovcloudapi.net - - - .database.chinacloudapi.cn - Defines whether query metadata caching is enabled. @@ -4746,4 +4734,31 @@ Invalid attempt to get JsonDocument on column '{0}'. JsonDocument is only supported for columns of type json. + + Buffer advanced too far {0} + + + Buffer maximum size exceeded: {0} + + + Missing the DataColumn '{0}' for the SourceColumn '{2}'. + + + SqlBatchCommand list has not been initialized. + + + Invalid attempt to get vector data from column '{0}'. Vectors are only supported for columns of type vector. + + + Unsupported Vector type '{0}'. + + + 'null' value not supported for output parameter '{0}' of SqlDbtype Vector. + + + Invalid vector header received. + + + {0} Invalid JSON string for vector. + diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx index 780eda099f..89ed2f634d 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.ru.resx @@ -231,18 +231,12 @@ Нельзя использовать слово Credential с ключевым словом строки соединения Integrated Security. - - Нельзя использовать слово Credential с ключевым словом Context Connection. - Невозможно задать свойство AccessToken, если в строке подключения указаны ключевые слова "UserID", "UID", "Password" или "PWD". Невозможно задать свойство AccessToken, если для ключевого слова строки подключения "Integrated Security" заданы значения "true" или "SSPI". - - Невозможно задать свойство AccessToken с ключевым словом "Context Connection". - Невозможно задать свойство AccessToken, если свойство Credential уже задано. @@ -1845,9 +1839,6 @@ Не поддающееся преобразованию несоответствие типов между SourceColumn "{0}" из {1} и DataColumn "{2}" из {3}. - - Отсутствует DataColumn "{0}" для данного SourceColumn "{2}". - В DataTable "{1}" отсутствует DataColumn "{0}" для данного SourceColumn "{2}". @@ -2076,9 +2067,6 @@ Недопустимое смещение в конечном буфере (размер: {0}): {1} - - Отсутствуют данные для строки или столбца. - Численное значение не умещается в 96-разрядном десятичном числе. @@ -2532,6 +2520,9 @@ Для этой команды необходимо асинхронное подключение. Укажите в строке подключения «Asynchronous Processing=true». + + Истекло время ожидания для попытки подключения. + Истекло время ожидания. Подключение разорвано. @@ -2781,30 +2772,6 @@ Длина данных "{0}" меньше 0. - - Недопустимая попытка вызвать метод {0}, когда закрыт SqlResultSet. - - - Невозможно завершить операцию, поскольку SqlResultSet закрыт. - - - Нельзя вызвать "{0}", если запись доступна только для чтения. - - - Невозможно завершить операцию, поскольку запись доступна только для чтения. - - - Недопустимая попытка вызвать метод {0}, когда текущая строка удалена. - - - Невозможно завершить операцию, поскольку текущая строка удалена. - - - Невозможно завершить операцию, поскольку команда, создавшая SqlResultSet, отделена от исходного подключения. SqlResultSet закрыт. - - - Для данного запроса не удалось создать SqlResultSet с нужными параметрами. - Нельзя вызвать "{0}", если SqlDataRecord доступна только для чтения. @@ -2874,51 +2841,21 @@ Загруженная библиотека System.Transactions.dll не поддерживает глобальные транзакции. Перейдите на .NET Framework 4.6.2 или более позднюю версию. - - Группировка обновлений для контекстного подключения не поддерживается. - - - Единственным дополнительным ключевым словом строки подключения, которое может использоваться при запросе контекстного подключения, является ключевое слово Type System Version. - - - Контекстное подключение не поддерживает Type System Version=SQL Server 2000. - Подключение к контексту с помощью Microsoft.Data.SqlClient не поддерживается. - - Контекстное подключение уже используется. - - - Для затребованной операции необходим контекст SqlClr, который доступен только при выполнении в процессе Sql Server. - - - Для затребованной операции необходим поток выполнения Sql Server. Текущий поток был запущен кодом пользователя или другим кодом, который не является кодом модуля Sql Server. - Вложенные TransactionScopes не поддерживаются. - - Для контекстного подключения затребованная операция не доступна. - - - Для контекстного подключения уведомления не доступны. - Недопустимое серверное событие: {0}. - - Пользовательские экземпляры не разрешены при выполнении в процессе Sql Server. - Длина "{0}" должна соответствовать длине "{1}". SqlDbType "{0}" недопустим для {1}. Поддерживается только {2}. - - Произошла ошибка, связанная с предыдущей строкой, отправленной в SqlPipe. Перед отправкой других строк необходимо вызвать SendResultsEnd. - Слишком много значений. @@ -3666,9 +3603,6 @@ Этот объект SqlCommand уже связан с другим объектом SqlDependency. - - Объект SqlDependency не может быть создан при выполнении внутри процесса SQL Server. - SQL Server Service Broker для текущей базы данных не включен, поэтому оповещения не поддерживаются. Включите Service Broker для этой базы данных, чтобы использовать оповещения. @@ -3729,21 +3663,6 @@ В перечислении SqlDataRecord нет записей. Чтобы передать имеющий табличное значение параметр table-valued parameter, который не содержит строк, используйте неопределенную ссылку null на значение. - - SqlPipe не поддерживает выполнения команды с подключением, которое не является контекстным. - - - Длина сообщения {0} превышает максимальное поддерживаемое значение 4000. - - - Не удалось использовать канал, поскольку он занят другой операцией. - - - Результирующий набор данных сейчас отправляется в канал. Перед вызовом {0} необходимо завершить текущий результирующий набор данных. - - - Результирующий набор данных не инициализирован. Вызовите SendResultSetStart перед вызовом {0}. - Поставщик HTTP @@ -3808,7 +3727,7 @@ Сбой при сопоставлении порта механизму завершения ввода-вывода - + Сокет имеет значение NULL Ошибка времени ожидания @@ -4446,18 +4365,6 @@ Задает поведение периода блокировки для пула подключений. - - .database.windows.net - - - .database.cloudapi.de - - - .database.usgovcloudapi.net - - - .database.chinacloudapi.cn - Определяет, включено ли кэширование метаданных запросов. @@ -4746,4 +4653,31 @@ Недопустимая попытка получить JsonDocument для столбца "{0}". JsonDocument поддерживается только для столбцов типа JSON. + + Буфер расширен чрезмерно {0} + + + Превышен максимальный размер буфера: {0} + + + Отсутствует DataColumn "{0}" для данного SourceColumn "{2}". + + + Список SqlBatchCommand не инициализирован. + + + Недопустимая попытка получить векторные данные из столбца "{0}". Векторы поддерживаются только для столбцов векторного типа. + + + Неподдерживаемый тип вектора "{0}". + + + Значение null не поддерживается для выходного параметра "{0}" вектора SqlDbType. + + + Получен недопустимый заголовок вектора. + + + {0} Недопустимая строка JSON для вектора. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx index 1bd22a40f8..e8b2fcd99d 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.tr.resx @@ -231,18 +231,12 @@ Integrated Security anahtar sözcüğüyle Credential kullanılamaz. - - Context Connection anahtar sözcüğüyle Credential kullanılamaz. - 'UserID', 'UID', 'Password' veya 'PWD' bağlantı dizesinde belirtilmişse AccessToken özelliği ayarlanamaz. 'Integrated Security' bağlantı dizesi anahtar sözcüğü 'true' veya 'SSPI' olarak ayarlanmışsa AccessToken özelliği ayarlanamaz. - - 'Context Connection' anahtar sözcüğü ile AccessToken özelliği ayarlanamaz. - Credential özelliği zaten ayarlanmışsa AccessToken özelliği ayarlanamaz. @@ -1845,9 +1839,6 @@ '{1}' / {0} öğesine ait SourceColumn ile '{3}' / {2} öğesine ait DataColumn arasında dönüştürülemez tür uyuşmazlığı. - - SourceColumn '{2}' için DataColumn '{0}' eksik. - SourceColumn '{2}' için DataTable '{1}' içinde DataColumn '{0}' eksik. @@ -2076,9 +2067,6 @@ Geçersiz hedef arabellek (boyutu {0}) uzaklığı: {1} - - Satır/sütun için hiç veri yok. - Sayı değeri 96 bit ondalık basamağa sığmayacak kadar büyük. @@ -2532,6 +2520,9 @@ Bu komut, zaman uyumsuz bağlantı gerektiriyor. Bağlantı dizesinde “Asynchronous Processing=true” ayarını yapın. + + Bağlantı denemesi zaman aşımına uğradı. + Zaman aşımı süresi sona erdi. Sonuç olarak bağlantı kesildi. @@ -2781,30 +2772,6 @@ Veri uzunluğu '{0}' 0'dan küçük. - - SqlResultSet kapalıyken geçersiz bir {0} metodu çağırma girişimi. - - - SqlResultSet kapalı olduğundan işlem tamamlanamıyor. - - - Kayıt salt okunur olduğunda '{0}' çağrılamaz. - - - Kayıt salt okunur olduğundan işlem tamamlanamıyor. - - - Geçerli satır silinmişken geçersiz bir {0} metodunu çağırma girişimi - - - Geçerli satır silindiğinden işlem tamamlanamıyor - - - SqlResultSet'i oluşturan komutun özgün bağlantıyla ilişkisi kesildiği için işlem tamamlanamıyor. SqlResultSet kapalı. - - - SqlResultSet, verilen sorgu için istenen seçeneklerle oluşturulamadı. - SqlDataRecord salt okunur olduğunda '{0}' çağrılamaz. @@ -2874,51 +2841,21 @@ Şu anda yüklü olan System.Transactions.dll, Genel İşlemleri desteklemiyor. Lütfen .NET Framework 4.6.2 veya sonraki bir sürüme yükseltin. - - Güncelleştirmelerin toplu işlenmesi, içerik bağlantısında desteklenmiyor. - - - İçerik bağlantısı istenirken kullanılabilecek tek ek bağlantı dizesi anahtar sözcüğü, Type System Version anahtar sözcüğüdür. - - - İçerik bağlantısı, Type System Version=SQL Server 2000 desteklemiyor. - Microsoft.Data.SqlClient kullanarak bağlam bağlantısına bağlanma desteklenmiyor. - - İçerik bağlantısı zaten kullanılıyor. - - - İstenen işlem, yalnızca Sql Server işleminde çalışılırken kullanılabilen bir SqlClr içeriği gerektiriyor. - - - İstenen işlem bir Sql Server yürütme iş parçacığı gerektirir. Geçerli iş parçacığı kullanıcı kodu veya diğer Sql Server motoru olmayan kodlar tarafından başlatılmıştır. - İç içe TransactionScopes desteklenmiyor. - - İstenen işlem içerik bağlantısında kullanılamıyor. - - - İçerik bağlantısında bildirim kullanılamıyor. - Beklenmeyen sunucu olayı: {0}. - - Sql Server işleminde çalışırken kullanıcı örneklerine izin verilmez. - ‘{0}’ uzunluğu, ‘{1}’ uzunluğu ile aynı olmalıdır. SqlDbType '{0}' {1} için geçersiz. Yalnızca {2} desteklenir. - - SqlPipe'a gönderilen önceki satırla ilgili bir hata oluştu. Başka bir şey gönderilmeden önce SendResultsEnd çağrılmalıdır. - Çok fazla değer. @@ -3666,9 +3603,6 @@ Bu SqlCommand nesnesi, zaten başka bir SqlDependency nesnesiyle ilişkilendirildi. - - SqlDependency nesnesi, SQL Server işleminde çalışılırken oluşturulamaz. - Geçerli veritabanı için SQL Server Hizmet Aracısı etkin değil ve sonuç olarak sorgu bildirimleri desteklenmiyor. Bildirimleri kullanmak istiyorsanız lütfen bu veritabanı için Hizmet Aracısını etkinleştirin. @@ -3729,21 +3663,6 @@ SqlDataRecord numaralandırması içinde kayıt yok. Satır içermeyen tablo değerli bir parametre göndermek için değer olarak bunun yerine bir null başvuru kullanın. - - SqlPipe, içerik bağlantısı olmayan bir bağlantıyla komut yürütmeyi desteklemez. - - - İleti uzunluğu {0}, desteklenen en fazla uzunluk olan 4000'i aşıyor. - - - Kanal, başka bir işlemle meşgulken kullanılamadı. - - - Şu anda kanala bir sonuç kümesi gönderiliyor. {0}‘i çağırmadan önce geçerli sonuç kümesini sonlandırın. - - - Sonuç kümesi başlatılmadı. {0}'i çağırmadan önce SendResultSetStart'ı çağırın. - HTTP Sağlayıcısı @@ -3808,7 +3727,7 @@ Bağlantı noktasını G/Ç tamamlama mekanizması ile ilişkilendirme hatası - + Yuva değeri null Zaman aşımı hatası @@ -4446,18 +4365,6 @@ Bir bağlantı havuzundaki bağlantılar için engelleme dönemi davranışını tanımlar. - - .database.windows.net - - - .database.cloudapi.de - - - .database.usgovcloudapi.net - - - .database.chinacloudapi.cn - Sorgu meta verilerini önbelleğe almanın etkin olup olmadığını tanımlar. @@ -4746,4 +4653,31 @@ '{0}' sütununda JsonDocument alma denemesi geçersiz. JsonDocument yalnızca json türündeki sütunlar için desteklenir. + + Arabellek çok fazla ilerledi {0} + + + Arabellek boyut üst sınırı aşıldı: {0} + + + SourceColumn '{2}' için DataColumn '{0}' eksik. + + + SqlBatchCommand listesi başlatılmadı. + + + '{0}' sütunundan vektör verilerini alma denemesi geçersiz. Vektörler yalnızca vektör türündeki sütunlar için desteklenir. + + + '{0}' vektör türü desteklenmiyor. + + + SqlDbType Vector'ın '{0}' çıkış parametresi için 'null' değeri desteklenmiyor. + + + Geçersiz vektör başlığı alındı. + + + Vektör JSON dizesi {0} geçersiz. + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx index a1d545d107..d21b03bca3 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hans.resx @@ -231,18 +231,12 @@ 无法使用含有 Integrated Security 连接字符串关键字的 Credential。 - - 无法使用含有 Context Connection 关键字的 Credential。 - 如果在连接字符串中指定了 "UserID"、"UID"、"Password" 或 "PWD",则无法设置 AccessToken 属性。 如果 "Integrated Security" 连接字符串关键字设置为 "true" 或 "SSPI",则无法设置 AccessToken 属性。 - - 无法设置使用 "Context Connection" 关键字的 AccessToken 属性。 - 如果已设置 Credential 属性,则无法设置 AccessToken 属性。 @@ -1845,9 +1839,6 @@ {1} 的 SourceColumn“{0}”和 {3} 的 DataColumn“{2}”之间无法转换的类型不匹配。 - - 缺少 SourceColumn“{2}”的 DataColumn“{0}”。 - DataTable“{1}”中缺少 SourceColumn“{2}”的 DataColumn“{0}”。 @@ -2076,9 +2067,6 @@ 无效的目标缓冲区(大小为 {0})偏移量: {1} - - 不存在此行/列的数据。 - 枚举值太大,不能适应 96 位十进制。 @@ -2532,6 +2520,9 @@ 此命令要求异步连接。请在连接字符串中设置“Asynchronous Processing=true”。 + + 连接尝试超时。 + 超时时间已到。因此,连接被中断。 @@ -2781,30 +2772,6 @@ 数据长度“{0}”小于 0。 - - SqlResultSet 关闭时,调用方法 {0} 的尝试无效。 - - - 不能完成操作,因为 SqlResultSet 已关闭。 - - - 记录为只读时不能调用“{0}”。 - - - 不能完成操作,因为记录为只读。 - - - 当前列被删除时,调用方法 {0} 的尝试无效 - - - 不能完成操作,因为当前列已被删除。 - - - 不能完成操作,因为创建 SqlResultSet 的命令已经与原始连接取消关联。SqlResultSet 已关闭。 - - - 不能使用期望选项为给定查询创建 SqlResultSet。 - SqlDataRecord 为只读时,不能调用“{0}”。 @@ -2874,51 +2841,21 @@ 当前加载的 System.Transactions.dll 不支持全局事务。请升级到 .NET Framework 4.6.2 或更高版本。 - - 在上下文连接上不支持批处理更新。 - - - 在请求上下文连接时唯一可以使用的其他连接字符串关键字是 Type System Version 关键字。 - - - 上下文连接不支持 Type System Version=SQL Server 2000。 - 不支持使用 Microsoft.Data.SqlClient 连接到上下文连接。 - - 上下文连接已经在使用。 - - - 所请求的操作需要使用 SqlClr 上下文,该上下文只有在 Sql Server 进程中运行时才可用。 - - - 所请求的操作需要使用 Sql Server 执行线程。当前线程由用户代码或其他非 Sql Server 引擎代码启动。 - 不支持嵌套的 TransactionScopes。 - - 所请求的操作在上下文连接上不可用。 - - - 通知在上下文连接上不可用。 - 意外的服务器事件: {0}。 - - 在 Sql Server 进程中运行时不允许使用用户实例。 - “{0}”的长度必须与“{1}”的长度匹配。 SqlDbType“{0}”对于 {1} 无效。只支持 {2}。 - - 以前发送到 SqlPipe 的某行出错。必须先调用 SendResultsEnd,才能发送其他内容。 - 值太多。 @@ -3666,9 +3603,6 @@ 此 SqlCommand 对象已与另一 SqlDependency 对象关联。 - - 在 SQL Server 进程内运行时,无法创建 SqlDependency 对象。 - 未启用当前数据库的 SQL Server Service Broker,因此查询通知不受支持。如果希望使用通知,请为此数据库启用 Service Broker。 @@ -3729,21 +3663,6 @@ SqlDataRecord 枚举中没有记录。要发送不包含行的 table-valued parameter,请对该值改用 null 引用。 - - SqlPipe 不支持使用非上下文连接执行命令。 - - - 消息长度 {0} 超过支持的最大长度 4000。 - - - 无法使用该管道,该管道正在处理其他操作。 - - - 结果集当前正在发送到该管道。在调用 {0} 之前结束当前结果集。 - - - 结果集尚未启动。在调用 {0} 之前调用 SendResultSetStart。 - HTTP 提供程序 @@ -3808,7 +3727,7 @@ 端口与 I/O 关联的完成机制失败 - + 套接字为 null 超时错误 @@ -4446,18 +4365,6 @@ 定义针对连接池的暂停时段行为。 - - .database.windows.net - - - .database.cloudapi.de - - - .database.usgovcloudapi.net - - - .database.chinacloudapi.cn - 定义是否启用查询元数据缓存。 @@ -4746,4 +4653,31 @@ 在列 ‘{0}’ 上获取 JsonDocument 的尝试无效。只有 json 类型的列支持 JsonDocument。 + + 缓冲区过大 {0} + + + 超出缓冲区最大大小: {0} + + + 缺少 SourceColumn“{2}”的 DataColumn“{0}”。 + + + SqlBatchCommand 列表尚未初始化。 + + + 从列“{0}”获取矢量数据的尝试无效。仅矢量类型的列支持矢量。 + + + 矢量类型“{0}”不受支持。 + + + SqlDbtype 向量的输出参数“{0}”不支持 "null" 值。 + + + 收到的矢量标头无效。 + + + {0} 矢量的 JSON 字符串无效。 + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx index e6b398922b..2f41424cf7 100644 --- a/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx +++ b/src/Microsoft.Data.SqlClient/src/Resources/Strings.zh-Hant.resx @@ -231,18 +231,12 @@ 無法將 Credential 與 Integrated Security 連接字串關鍵字搭配使用。 - - 無法將 Credential 與 Context Connection 關鍵字搭配使用。 - 如果連接字串中指定了 'UserID'、'UID'、'Password' 或 'PWD',就無法設定 AccessToken 屬性。 如果 'Integrated Security' 連接字串關鍵字已設為 'true' 或 'SSPI',就無法設定 AccessToken 屬性。 - - 無法使用 'Context Connection' 關鍵字設定 AccessToken 屬性。 - 如果已設定 Credential 屬性,就無法設定 AccessToken 屬性。 @@ -1845,9 +1839,6 @@ {1} 的 SourceColumn '{0}' 與 {3} 的 DataColumn '{2}' 之間發生不可轉換類型不符的問題。 - - 遺漏 SourceColumn '{2}' 的 DataColumn '{0}'。 - 在 DataTable '{1}' 中遺漏 SourceColumn '{2}' 的 DataColumn '{0}'。 @@ -2076,9 +2067,6 @@ 無效的目的緩衝區 ({0} 的大小) 位移: {1} - - 資料列/資料行沒有資料。 - 數值太大,超過 96 位元小數。 @@ -2532,6 +2520,9 @@ 此命令需要非同步連接。請在連接字串中設定 "Asynchronous Processing=true"。 + + 連接嘗試逾時。 + 等候逾時已過時。結果為已連接中斷。 @@ -2781,30 +2772,6 @@ 資料長度 '{0}' 小於 0。 - - 當 SqlResultSet 已關閉時,嘗試呼叫方法 {0} 失敗。 - - - 無法完成操作,因為 SqlResultSet 已關閉。 - - - 當記錄為唯讀時無法呼叫 '{0}'。 - - - 無法完成操作,因為記錄為唯讀。 - - - 當目前的資料列已刪除時,嘗試呼叫方法 {0} 無效 - - - 無法完成操作,因為目前的資料列已刪除。 - - - 無法完成操作,因為已經從原始的連接中解除建立 SqlResultSet 命令的關聯。SqlResultSet 已關閉。 - - - 無法建立包含欲查選項指定查詢的 SqlResultSet。 - 當 SqlDataRecord 為唯讀時,無法呼叫 '{0}'。 @@ -2874,51 +2841,21 @@ 目前載入的 System.Transactions.dll 不支援全域交易。請升級為 .NET Framework 4.6.2 或更新版本。 - - 內容連接上並不支援批次更新。 - - - Type System Version 關鍵字是唯一在要求內容連接時可能需要使用的其他連接字串關鍵字。 - - - 此內容連接不支援 Type System Version=SQL Server 2000。 - 不支援使用 Microsoft.Data.SqlClient 連線至內容連線。 - - 此內容連接已在使用中。 - - - 要求的操作需要 SqlClr 內容,這是在 Sql Server 程序中執行時唯一可用的內容。 - - - 要求的操作需要 Sql Server 執行緒。目前的執行緒是由使用者編碼或其他非 Sql Server 引擎碼所啟動的。 - 不支援巢狀 TransactionScopes。 - - 在此內容連接上無法使用要求的操作。 - - - 在此內容連接尚無法使用通知。 - 未預期的伺服器事件: {0}。 - - 在 Sql Server 程序中執行時並不允許使用者執行個體。 - '{0}' 的長度必須和 '{1}' 的長度相符。 SqlDbType '{0}' 對 {1} 是無效的。只支援 {2}。 - - 發生錯誤,已傳送優先資料列至 SqlPipe。在進行其他傳送之前,必須先呼叫 SendResultsEnd。 - 太多數值。 @@ -3666,9 +3603,6 @@ SqlCommand 物件已與其他 SqlDependency 物件關聯。 - - 在 SQL Server 程序中執行時,SqlDependency 物件就不能建立。 - 尚未啟用目前資料庫的 SQL Server Service Broker,因此不支援查詢通知。如果您想要使用通知,請啟用這個資料庫的 Service Broker。 @@ -3729,21 +3663,6 @@ SqlDataRecord 列舉之中沒有記錄。若要傳送沒有資料列的 table-valued parameter,請使用該值的 null 參考來代替。 - - 當某命令的連接不是內容連接時,SqlPipe 不支援該命令的執行。 - - - 訊息長度 {0} 超過支援的最大長度 4000。 - - - 當管線忙碌於其他操作時便無法使用。 - - - 正傳送結果集至管線中。在呼叫 {0} 前請先結束目前的結果集。 - - - 結果集尚未初始化。在呼叫 {0} 前請先呼叫 SendResultSetStart。 - HTTP 提供者 @@ -3808,7 +3727,7 @@ 關聯連接埠與 I/O 完成機制失敗 - + 通訊端為 null 逾時錯誤 @@ -4446,18 +4365,6 @@ 為連接集區定義封鎖期間行為。 - - .database.windows.net - - - .database.cloudapi.de - - - .database.usgovcloudapi.net - - - .database.chinacloudapi.cn - 定義是否啟用查詢中繼資料快取。 @@ -4746,4 +4653,31 @@ 在資料行 '{0}' 上取得 JsonDocument 的嘗試無效。JsonDocument 僅支援 JSON 類型的資料行。 + + 緩衝區進階超出範圍 {0} + + + 已超過緩衝區大小上限: {0} + + + 遺漏 SourceColumn '{2}' 的 DataColumn '{0}'。 + + + SqlBatchCommand 列表尚未初始化。 + + + 從資料行 '{0}' 取得向量資料的嘗試無效。向量僅支援向量類型的資料行。 + + + 不支援的向量類型 '{0}'。 + + + SqlDbType 向量的輸出參數 '{0}' 不支援 'null' 值。 + + + 收到的向量標頭無效。 + + + {0} 向量的 JSON 字串無效。 + \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/src/System/Buffers/ArrayBufferWriter.netfx.cs b/src/Microsoft.Data.SqlClient/src/System/Buffers/ArrayBufferWriter.netfx.cs new file mode 100644 index 0000000000..b23060201b --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/System/Buffers/ArrayBufferWriter.netfx.cs @@ -0,0 +1,261 @@ +// 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. + +#if NETFRAMEWORK + +using System.Diagnostics; +using System.Globalization; + +namespace System.Buffers +{ + /// + /// Internal implementation of for platforms that don't have it available. + /// + /// NOTE: This file should not be edited as it is copied from dotnet/runtime and has no tests in this repo. + /// It is only included for netfx version of the driver because this class was not introduced + /// until netstandard 2.1. + /// + /// + internal sealed class ArrayBufferWriter : IBufferWriter + { + // Copy of Array.MaxLength. + // Used by projects targeting .NET Framework. + private const int ArrayMaxLength = 0x7FFFFFC7; + + private const int DefaultInitialBufferSize = 256; + + private T[] _buffer; + private int _index; + + /// + /// Creates an instance of an , in which data can be written to, + /// with the default initial capacity. + /// + public ArrayBufferWriter() + { + _buffer = Array.Empty(); + _index = 0; + } + + /// + /// Creates an instance of an , in which data can be written to, + /// with an initial capacity specified. + /// + /// The minimum capacity with which to initialize the underlying buffer. + /// + /// Thrown when is not positive (i.e. less than or equal to 0). + /// + public ArrayBufferWriter(int initialCapacity) + { + if (initialCapacity <= 0) + throw new ArgumentException(null, nameof(initialCapacity)); + + _buffer = new T[initialCapacity]; + _index = 0; + } + + /// + /// Returns the data written to the underlying buffer so far, as a . + /// + public ReadOnlyMemory WrittenMemory => _buffer.AsMemory(0, _index); + + /// + /// Returns the data written to the underlying buffer so far, as a . + /// + public ReadOnlySpan WrittenSpan => _buffer.AsSpan(0, _index); + + /// + /// Returns the amount of data written to the underlying buffer so far. + /// + public int WrittenCount => _index; + + /// + /// Returns the total amount of space within the underlying buffer. + /// + public int Capacity => _buffer.Length; + + /// + /// Returns the amount of space available that can still be written into without forcing the underlying buffer to grow. + /// + public int FreeCapacity => _buffer.Length - _index; + + /// + /// Clears the data written to the underlying buffer. + /// + /// + /// + /// You must reset or clear the before trying to re-use it. + /// + /// + /// The method is faster since it only sets to zero the writer's index + /// while the method additionally zeroes the content of the underlying buffer. + /// + /// + /// + public void Clear() + { + Debug.Assert(_buffer.Length >= _index); + _buffer.AsSpan(0, _index).Clear(); + _index = 0; + } + + /// + /// Resets the data written to the underlying buffer without zeroing its content. + /// + /// + /// + /// You must reset or clear the before trying to re-use it. + /// + /// + /// If you reset the writer using the method, the underlying buffer will not be cleared. + /// + /// + /// + public void ResetWrittenCount() => _index = 0; + + /// + /// Notifies that amount of data was written to the output / + /// + /// + /// Thrown when is negative. + /// + /// + /// Thrown when attempting to advance past the end of the underlying buffer. + /// + /// + /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. + /// + public void Advance(int count) + { + if (count < 0) + throw new ArgumentException(null, nameof(count)); + + if (_index > _buffer.Length - count) + ThrowInvalidOperationException_AdvancedTooFar(_buffer.Length); + + _index += count; + } + + + + /// + /// Returns a to write to that is at least the requested length (specified by ). + /// If no is provided (or it's equal to 0), some non-empty buffer is returned. + /// + /// + /// Thrown when is negative. + /// + /// + /// + /// This will never return an empty . + /// + /// + /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. + /// + /// + /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. + /// + /// + /// If you reset the writer using the method, this method may return a non-cleared . + /// + /// + /// If you clear the writer using the method, this method will return a with its content zeroed. + /// + /// + public Memory GetMemory(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + Debug.Assert(_buffer.Length > _index); + return _buffer.AsMemory(_index); + } + + /// + /// Returns a to write to that is at least the requested length (specified by ). + /// If no is provided (or it's equal to 0), some non-empty buffer is returned. + /// + /// + /// Thrown when is negative. + /// + /// + /// + /// This will never return an empty . + /// + /// + /// There is no guarantee that successive calls will return the same buffer or the same-sized buffer. + /// + /// + /// You must request a new buffer after calling Advance to continue writing more data and cannot write to a previously acquired buffer. + /// + /// + /// If you reset the writer using the method, this method may return a non-cleared . + /// + /// + /// If you clear the writer using the method, this method will return a with its content zeroed. + /// + /// + public Span GetSpan(int sizeHint = 0) + { + CheckAndResizeBuffer(sizeHint); + Debug.Assert(_buffer.Length > _index); + return _buffer.AsSpan(_index); + } + + private void CheckAndResizeBuffer(int sizeHint) + { + if (sizeHint < 0) + throw new ArgumentException(nameof(sizeHint)); + + if (sizeHint == 0) + { + sizeHint = 1; + } + + if (sizeHint > FreeCapacity) + { + int currentLength = _buffer.Length; + + // Attempt to grow by the larger of the sizeHint and double the current size. + int growBy = Math.Max(sizeHint, currentLength); + + if (currentLength == 0) + { + growBy = Math.Max(growBy, DefaultInitialBufferSize); + } + + int newSize = currentLength + growBy; + + if ((uint)newSize > int.MaxValue) + { + // Attempt to grow to ArrayMaxLength. + uint needed = (uint)(currentLength - FreeCapacity + sizeHint); + Debug.Assert(needed > currentLength); + + if (needed > ArrayMaxLength) + { + ThrowOutOfMemoryException(needed); + } + + newSize = ArrayMaxLength; + } + + Array.Resize(ref _buffer, newSize); + } + + Debug.Assert(FreeCapacity > 0 && FreeCapacity >= sizeHint); + } + + private static void ThrowInvalidOperationException_AdvancedTooFar(int capacity) + { + throw new OutOfMemoryException(string.Format(CultureInfo.InvariantCulture, Strings.ArrayBufferWriterAdvancedTooFar, capacity)); + } + + private static void ThrowOutOfMemoryException(uint capacity) + { + throw new OutOfMemoryException(string.Format(CultureInfo.InvariantCulture, Strings.ArrayBufferWriterMaximumSizeExceeded, capacity)); + } + } +} + +#endif + diff --git a/src/Microsoft.Data.SqlClient/src/System/Runtime/CompilerServices/IsExternalInit.netfx.cs b/src/Microsoft.Data.SqlClient/src/System/Runtime/CompilerServices/IsExternalInit.netfx.cs new file mode 100644 index 0000000000..0d0181ba6d --- /dev/null +++ b/src/Microsoft.Data.SqlClient/src/System/Runtime/CompilerServices/IsExternalInit.netfx.cs @@ -0,0 +1,23 @@ +// 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. + +#if NETFRAMEWORK + +using System.ComponentModel; + + +// This class enables the use of the `init` property accessor in .NET framework. +namespace System.Runtime.CompilerServices +{ + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class IsExternalInit + { + } +} + +#endif diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj b/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj new file mode 100644 index 0000000000..4133622d20 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj @@ -0,0 +1,60 @@ + + + + Common + netfx + netcoreapp + win + win-$(Platform) + $(ObjFolder)$(Configuration).$(Platform).$(AssemblyName) + $(BinFolder)$(Configuration).$(Platform).$(AssemblyName) + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + PreserveNewest + xunit.runner.json + + + + + + + + + + PreserveNewest + %(Filename)%(Extension) + + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs new file mode 100644 index 0000000000..f70560be9e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs @@ -0,0 +1,70 @@ +// 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; +using System.Collections.Generic; +using Azure.Core; +using Azure.Security.KeyVault.Keys; + +namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures; + +/// +/// Provides a base class for managing Azure Key Vault keys in test fixtures. +/// +/// +/// This class simplifies the creation and cleanup of RSA keys in an Azure Key Vault during testing +/// scenarios. It ensures that any keys created during the fixture's lifetime are properly deleted when the fixture is +/// disposed. +/// +public abstract class AzureKeyVaultKeyFixtureBase : IDisposable +{ + private readonly KeyClient _keyClient; + private readonly Random _randomGenerator; + + private readonly List _createdKeys = new List(); + + protected AzureKeyVaultKeyFixtureBase(Uri keyVaultUri, TokenCredential keyVaultToken) + { + _keyClient = new KeyClient(keyVaultUri, keyVaultToken); + _randomGenerator = new Random(); + } + + protected Uri CreateKey(string name, int keySize) + { + CreateRsaKeyOptions createOptions = new CreateRsaKeyOptions(GenerateUniqueName(name)) { KeySize = keySize }; + KeyVaultKey created = _keyClient.CreateRsaKey(createOptions); + + _createdKeys.Add(created); + return created.Id; + } + + private string GenerateUniqueName(string name) + { + byte[] rndBytes = new byte[16]; + + _randomGenerator.NextBytes(rndBytes); + return name + "-" + BitConverter.ToString(rndBytes); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + foreach (KeyVaultKey key in _createdKeys) + { + try + { + _keyClient.StartDeleteKey(key.Name).WaitForCompletion(); + } + catch (Exception) + { + continue; + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs new file mode 100644 index 0000000000..9007796013 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs @@ -0,0 +1,320 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; + +namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures; + +/// +/// Provides a base class for managing the creation, storage, and disposal of X.509 certificates used in cryptographic +/// operations. This class is designed to facilitate scenarios where certificates need to be programmatically generated, +/// added to certificate stores, and cleaned up after use. +/// +/// +/// This class includes functionality for creating self-signed certificates with specific configurations, +/// adding certificates to system certificate stores, and ensuring proper cleanup of certificates to avoid resource +/// leaks. It is intended to be used as a base class for test fixtures or other scenarios requiring temporary +/// certificate management. +/// The class implements to ensure that any certificates added to +/// certificate stores are removed and properly disposed of when the object is no longer needed. +/// +public abstract class CertificateFixtureBase : IDisposable +{ + /// + /// Certificates must be created using this provider. Certificates created by PowerShell + /// using another provider aren't accessible from RSACryptoServiceProvider, which means + /// that we could not roundtrip between SqlColumnEncryptionCertificateStoreProvider and + /// SqlColumnEncryptionCspProvider. + /// + private const string CspProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"; + + private sealed class CertificateStoreContext + { + public List Certificates { get; } + + public StoreLocation Location { get; } + + public StoreName Name { get; } + + public CertificateStoreContext(StoreLocation location, StoreName name) + { + Certificates = new List(); + Location = location; + Name = name; + } + } + + private readonly List _certificateStoreModifications = new List(); + + protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable dnsNames, IEnumerable ipAddresses, bool forceCsp = false) + { + // This will always generate a certificate with: + // * Start date: 24hrs ago + // * End date: 24hrs in the future + // * Subject: {subjectName} + // * Subject alternative names: {dnsNames}, {ipAddresses} + // * Public key: 2048-bit RSA + // * Hash algorithm: SHA256 + // * Key usage: digital signature, key encipherment + // * Enhanced key usage: server authentication, client authentication + DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddDays(-1); + DateTimeOffset notAfter = DateTimeOffset.UtcNow.AddDays(1); + byte[] passwordBytes = new byte[32]; + string password = null; + Random rnd = new Random(); + + rnd.NextBytes(passwordBytes); + password = Convert.ToBase64String(passwordBytes); +#if NET + X500DistinguishedNameBuilder subjectBuilder = new X500DistinguishedNameBuilder(); + SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder(); + RSA rsaKey = CreateRSA(forceCsp); + bool hasSans = false; + + subjectBuilder.AddCommonName(subjectName); + foreach (string dnsName in dnsNames) + { + sanBuilder.AddDnsName(dnsName); + hasSans = true; + } + foreach (string ipAddress in ipAddresses) + { + sanBuilder.AddIpAddress(System.Net.IPAddress.Parse(ipAddress)); + hasSans = true; + } + + CertificateRequest request = new CertificateRequest(subjectBuilder.Build(), rsaKey, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + + request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false)); + request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, false)); + request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection() { new Oid("1.3.6.1.5.5.7.3.1"), new Oid("1.3.6.1.5.5.7.3.2") }, true)); + + if (hasSans) + { + request.CertificateExtensions.Add(sanBuilder.Build()); + } + + // Generate an ephemeral certificate, then export it and return it as a new certificate with the correct key storage flags set. + // This is to ensure that it's imported into the certificate stores with its private key. + using (X509Certificate2 ephemeral = request.CreateSelfSigned(notBefore, notAfter)) + { +#if NET9_0_OR_GREATER + return X509CertificateLoader.LoadPkcs12( + ephemeral.Export(X509ContentType.Pkcs12, password), + password, + X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable, + new Pkcs12LoaderLimits(Pkcs12LoaderLimits.Defaults) + { + PreserveStorageProvider = true, + PreserveKeyName = true + }); +#else + return new X509Certificate2( + ephemeral.Export(X509ContentType.Pkcs12, password), + password, + X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); +#endif + } +#else + // The CertificateRequest API is available in .NET Core, but was only added to .NET Framework 4.7.2; it thus can't be used in the test projects. + // Instead, fall back to running a PowerShell script which calls New-SelfSignedCertificate. This cmdlet also adds the certificate to a specific, + // certificate store, so remove it from there. + // Normally, the PowerShell script will return zero and print the base64-encoded certificate to stdout. If there's an exception, it'll return 1 and + // print the message instead. + const string PowerShellCommandTemplate = @"$notBefore = [DateTime]::ParseExact(""{0}"", ""O"", $null) +$notAfter = [DateTime]::ParseExact(""{1}"", ""O"", $null) +$subject = ""CN={2}"" +$sAN = @({3}) + +try +{{ + $x509 = PKI\New-SelfSignedCertificate -Subject $subject -TextExtension $sAN -KeyLength 2048 -KeyAlgorithm RSA ` + -CertStoreLocation ""Cert:\CurrentUser\My"" -NotBefore $notBefore -NotAfter $notAfter ` + -KeyExportPolicy Exportable -HashAlgorithm SHA256 -Provider ""{5}"" -KeySpec KeyExchange + + if ($x509 -eq $null) + {{ throw ""Certificate was null!"" }} + + $exportedArray = $x509.Export(""Pkcs12"", ""{4}"") + Write-Output $([Convert]::ToBase64String($exportedArray)) + + Remove-Item ""Cert:\CurrentUser\My\$($x509.Thumbprint)"" + + exit 0 +}} +catch [Exception] +{{ + Write-Output $_.Exception.Message + exit 1 +}}"; + const int PowerShellCommandTimeout = 15_000; + + string sanString = string.Empty; + bool hasSans = false; + + foreach (string dnsName in dnsNames) + { + sanString += string.Format("DNS={0}&", dnsName); + hasSans = true; + } + foreach (string ipAddress in ipAddresses) + { + sanString += string.Format("IPAddress={0}&", ipAddress); + hasSans = true; + } + + sanString = hasSans ? "\"2.5.29.17={text}" + sanString.Substring(0, sanString.Length - 1) + "\"" : string.Empty; + + string formattedCommand = string.Format(PowerShellCommandTemplate, notBefore.ToString("O"), notAfter.ToString("O"), subjectName, sanString, password, CspProviderName); + + ProcessStartInfo startInfo = new() + { + FileName = "powershell.exe", + RedirectStandardOutput = true, + RedirectStandardError = false, + UseShellExecute = false, + CreateNoWindow = true, + // Pass the Base64-encoded command to remove the need to escape quote marks + Arguments = "-EncodedCommand " + Convert.ToBase64String(Encoding.Unicode.GetBytes(formattedCommand)), + // Run as Administrator, since we're manipulating the system + // certificate store. + Verb = "RunAs", + LoadUserProfile = true + }; + + // This command sometimes fails with: + // + // Access is denied. 0x80070005 (WIN32: 5 ERROR_ACCESS_DENIED) + // + // We will retry it a few times with a short delay to avoid spurious + // failures in CI pipeline runs. + // + // See ADO issue for more details: + // + // Issue 34304: #3223 Fix Functional test failures in CI + // + // https://sqlclientdrivers.visualstudio.com/ADO.Net/_workitems/edit/34304 + // + // Delay 5 seconds between retries, and retry 3 times. + const int delay = 5; + const int retries = 3; + + string commandOutput = string.Empty; + + for (int attempt = 1; attempt <= retries; ++attempt) + { + using Process psProcess = new() { StartInfo = startInfo }; + + psProcess.Start(); + commandOutput = psProcess.StandardOutput.ReadToEnd(); + + if (!psProcess.WaitForExit(PowerShellCommandTimeout)) + { + psProcess.Kill(); + throw new Exception("Process did not complete in time, exiting."); + } + + // Process completed successfully if it had an exit code of zero, the command output will be the base64-encoded certificate + var code = psProcess.ExitCode; + if (code == 0) + { + return new X509Certificate2(Convert.FromBase64String(commandOutput), password, X509KeyStorageFlags.Exportable); + } + + Console.WriteLine( + $"PowerShell command failed with exit code {code} on " + + $"attempt {attempt} of {retries}; " + + $"retrying in {delay} seconds..."); + + Thread.Sleep(TimeSpan.FromSeconds(delay)); + } + + throw new Exception( + "PowerShell command raised exception: " + + $"{commandOutput}; command was: {formattedCommand}"); +#endif + } + +#if NET + private static RSA CreateRSA(bool forceCsp) + { + const int KeySize = 2048; + const int CspProviderType = 24; + + return forceCsp && OperatingSystem.IsWindows() + ? new RSACryptoServiceProvider(KeySize, new CspParameters(CspProviderType, CspProviderName, Guid.NewGuid().ToString())) + : RSA.Create(KeySize); + } +#endif + + protected void AddToStore(X509Certificate2 cert, StoreLocation storeLocation, StoreName storeName) + { + CertificateStoreContext storeContext = _certificateStoreModifications.Find(csc => csc.Location == storeLocation && csc.Name == storeName); + + if (storeContext == null) + { + storeContext = new(storeLocation, storeName); + _certificateStoreModifications.Add(storeContext); + } + + using X509Store store = new X509Store(storeContext.Name, storeContext.Location); + + store.Open(OpenFlags.ReadWrite); + if (store.Certificates.Contains(cert)) + { + store.Remove(cert); + } + store.Add(cert); + + storeContext.Certificates.Add(cert); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + foreach (CertificateStoreContext storeContext in _certificateStoreModifications) + { + using X509Store store = new X509Store(storeContext.Name, storeContext.Location); + + try + { + store.Open(OpenFlags.ReadWrite); + } + catch (Exception) + { + continue; + } + + foreach (X509Certificate2 cert in storeContext.Certificates) + { + try + { + if (store.Certificates.Contains(cert)) + { + store.Remove(cert); + } + } + catch (Exception) + { + continue; + } + + cert.Dispose(); + } + + storeContext.Certificates.Clear(); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs new file mode 100644 index 0000000000..a4aa84842f --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs @@ -0,0 +1,70 @@ +// 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.Security.Cryptography.X509Certificates; +using System.Security.Principal; + +namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures; + +/// +/// Provides a fixture for managing certificates used in column encryption scenarios. +/// +/// +/// This class creates and manages certificates for testing or operational purposes, including primary +/// and secondary column encryption certificates, as well as a certificate without a private key. Certificates are +/// added to the appropriate certificate stores based on the current user's permissions. +/// +public sealed class ColumnEncryptionCertificateFixture : CertificateFixtureBase +{ + public X509Certificate2 PrimaryColumnEncryptionCertificate { get; } + + public X509Certificate2 SecondaryColumnEncryptionCertificate { get; } + + public X509Certificate2 CertificateWithoutPrivateKey { get; } + + private readonly X509Certificate2 _currentUserCertificate; + private readonly X509Certificate2 _localMachineCertificate; + + public ColumnEncryptionCertificateFixture() + { + PrimaryColumnEncryptionCertificate = CreateCertificate(nameof(PrimaryColumnEncryptionCertificate), Array.Empty(), Array.Empty()); + SecondaryColumnEncryptionCertificate = CreateCertificate(nameof(SecondaryColumnEncryptionCertificate), Array.Empty(), Array.Empty()); + _currentUserCertificate = CreateCertificate(nameof(_currentUserCertificate), Array.Empty(), Array.Empty()); + using (X509Certificate2 createdCertificate = CreateCertificate(nameof(CertificateWithoutPrivateKey), Array.Empty(), Array.Empty())) + { + // This will strip the private key away from the created certificate +#if NET9_0_OR_GREATER + CertificateWithoutPrivateKey = X509CertificateLoader.LoadCertificate(createdCertificate.Export(X509ContentType.Cert)); +#else + CertificateWithoutPrivateKey = new X509Certificate2(createdCertificate.Export(X509ContentType.Cert)); +#endif + AddToStore(CertificateWithoutPrivateKey, StoreLocation.CurrentUser, StoreName.My); + } + + AddToStore(PrimaryColumnEncryptionCertificate, StoreLocation.CurrentUser, StoreName.My); + AddToStore(SecondaryColumnEncryptionCertificate, StoreLocation.CurrentUser, StoreName.My); + AddToStore(_currentUserCertificate, StoreLocation.CurrentUser, StoreName.My); + + if (IsAdmin) + { + _localMachineCertificate = CreateCertificate(nameof(_localMachineCertificate), Array.Empty(), Array.Empty()); + + AddToStore(_localMachineCertificate, StoreLocation.LocalMachine, StoreName.My); + } + } + + public X509Certificate2 GetCertificate(StoreLocation storeLocation) + { + return storeLocation == StoreLocation.CurrentUser + ? _currentUserCertificate + : storeLocation == StoreLocation.LocalMachine && IsAdmin + ? _localMachineCertificate + : throw new InvalidOperationException("Attempted to retrieve the certificate added to the local machine store; this requires administrator rights."); + } + + public static bool IsAdmin + => Environment.OSVersion.Platform == PlatformID.Win32NT + && new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs new file mode 100644 index 0000000000..a91ca7a0e0 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Security.Cryptography.X509Certificates; + +namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures; + +/// +/// Provides a test fixture for managing a column master key certificate. +/// +/// +/// This class is intended to simplify the setup and teardown of a column master key certificate for +/// testing purposes. It creates and optionally adds the certificate to the specified certificate store. +/// +public class ColumnMasterKeyCertificateFixture : CertificateFixtureBase +{ + public ColumnMasterKeyCertificateFixture() + : this(true) + { + } + + public X509Certificate2 ColumnMasterKeyCertificate { get; } + + protected ColumnMasterKeyCertificateFixture(bool createCertificate) + { + if (createCertificate) + { + ColumnMasterKeyCertificate = CreateCertificate(nameof(ColumnMasterKeyCertificate), Array.Empty(), Array.Empty()); + + AddToStore(ColumnMasterKeyCertificate, StoreLocation.CurrentUser, StoreName.My); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs new file mode 100644 index 0000000000..74c4ca0325 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs @@ -0,0 +1,54 @@ +// 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.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures; + +/// +/// Provides a fixture for working with certificates backed by a Cryptographic Service Provider (CSP). +/// +/// +/// This class creates and manages a certificate stored in the current user's certificate store, along +/// with its associated CSP key path. It is intended to facilitate testing or scenarios requiring temporary +/// certificates. +/// +public class CspCertificateFixture : CertificateFixtureBase +{ + public CspCertificateFixture() + { + CspCertificate = CreateCertificate(nameof(CspCertificate), Array.Empty(), Array.Empty(), true); + + AddToStore(CspCertificate, StoreLocation.CurrentUser, StoreName.My); + + CspCertificatePath = $"{StoreLocation.CurrentUser}/{StoreName.My}/{CspCertificate.Thumbprint}"; + CspKeyPath = GetCspPathFromCertificate(); + } + + public X509Certificate2 CspCertificate { get; } + + public string CspCertificatePath { get; } + + public string CspKeyPath { get; } + + private string GetCspPathFromCertificate() + { + RSA privateKey = CspCertificate.GetRSAPrivateKey(); + + if (privateKey is RSACryptoServiceProvider csp) + { + return string.Concat(csp.CspKeyContainerInfo.ProviderName, @"/", csp.CspKeyContainerInfo.KeyContainerName); + } + else if (privateKey is RSACng cng) + { + return string.Concat(cng.Key.Provider.Provider, @"/", cng.Key.KeyName); + } + else + { + return null; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs new file mode 100644 index 0000000000..2460b9234e --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs @@ -0,0 +1,412 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Microsoft.Data.SqlClient.Tests.Common; + +/// +/// This class provides read/write access to LocalAppContextSwitches values +/// for the duration of a test. It is intended to be constructed at the start +/// of a test and disposed of at the end. It captures the original values of +/// the switches and restores them when disposed. +/// +/// This follows the RAII pattern to ensure that the switches are always +/// restored, which is important for global state like LocalAppContextSwitches. +/// +/// https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization +/// +/// This class is not thread-aware and should not be used concurrently. +/// +public sealed class LocalAppContextSwitchesHelper : IDisposable +{ + #region Private Fields + + // These fields are used to expose LocalAppContextSwitches's properties. + private readonly PropertyInfo _legacyRowVersionNullBehaviorProperty; + private readonly PropertyInfo _suppressInsecureTlsWarningProperty; + private readonly PropertyInfo _makeReadAsyncBlockingProperty; + private readonly PropertyInfo _useMinimumLoginTimeoutProperty; + private readonly PropertyInfo _legacyVarTimeZeroScaleBehaviourProperty; + private readonly PropertyInfo _useConnectionPoolV2Property; + #if NETFRAMEWORK + private readonly PropertyInfo _disableTnirByDefaultProperty; + #endif + + // These fields are used to capture the original switch values. + private readonly FieldInfo _legacyRowVersionNullBehaviorField; + private readonly Tristate _legacyRowVersionNullBehaviorOriginal; + private readonly FieldInfo _suppressInsecureTlsWarningField; + private readonly Tristate _suppressInsecureTlsWarningOriginal; + private readonly FieldInfo _makeReadAsyncBlockingField; + private readonly Tristate _makeReadAsyncBlockingOriginal; + private readonly FieldInfo _useMinimumLoginTimeoutField; + private readonly Tristate _useMinimumLoginTimeoutOriginal; + private readonly FieldInfo _legacyVarTimeZeroScaleBehaviourField; + private readonly Tristate _legacyVarTimeZeroScaleBehaviourOriginal; + private readonly FieldInfo _useConnectionPoolV2Field; + private readonly Tristate _useConnectionPoolV2Original; + #if NETFRAMEWORK + private readonly FieldInfo _disableTnirByDefaultField; + private readonly Tristate _disableTnirByDefaultOriginal; + #endif + + #endregion + + #region Public Types + + /// + /// This enum is used to represent the state of a switch. + /// + /// It is a copy of the Tristate enum from LocalAppContextSwitches. + /// + public enum Tristate : byte + { + NotInitialized = 0, + False = 1, + True = 2 + } + + #endregion + + #region Construction + + /// + /// Construct to capture all existing switch values. + /// + /// + /// + /// Throws if any values cannot be captured. + /// + public LocalAppContextSwitchesHelper() + { + // Acquire a handle to the LocalAppContextSwitches type. + var assembly = typeof(SqlCommandBuilder).Assembly; + var switchesType = assembly.GetType( + "Microsoft.Data.SqlClient.LocalAppContextSwitches"); + if (switchesType == null) + { + throw new Exception("Unable to find LocalAppContextSwitches type."); + } + + // A local helper to acquire a handle to a property. + void InitProperty(string name, out PropertyInfo property) + { + var prop = switchesType.GetProperty( + name, BindingFlags.Public | BindingFlags.Static); + if (prop == null) + { + throw new Exception($"Unable to find {name} property."); + } + property = prop; + } + + // Acquire handles to all of the public properties of + // LocalAppContextSwitches. + InitProperty( + "LegacyRowVersionNullBehavior", + out _legacyRowVersionNullBehaviorProperty); + + InitProperty( + "SuppressInsecureTlsWarning", + out _suppressInsecureTlsWarningProperty); + + InitProperty( + "MakeReadAsyncBlocking", + out _makeReadAsyncBlockingProperty); + + InitProperty( + "UseMinimumLoginTimeout", + out _useMinimumLoginTimeoutProperty); + + InitProperty( + "LegacyVarTimeZeroScaleBehaviour", + out _legacyVarTimeZeroScaleBehaviourProperty); + + InitProperty( + "UseConnectionPoolV2", + out _useConnectionPoolV2Property); + + #if NETFRAMEWORK + InitProperty( + "DisableTnirByDefault", + out _disableTnirByDefaultProperty); + #endif + + // A local helper to capture the original value of a switch. + void InitField(string name, out FieldInfo field, out Tristate value) + { + var fieldInfo = + switchesType.GetField( + name, BindingFlags.NonPublic | BindingFlags.Static); + if (fieldInfo == null) + { + throw new Exception($"Unable to find {name} field."); + } + field = fieldInfo; + value = GetValue(field); + } + + // Capture the original value of each switch. + InitField( + "s_legacyRowVersionNullBehavior", + out _legacyRowVersionNullBehaviorField, + out _legacyRowVersionNullBehaviorOriginal); + + InitField( + "s_suppressInsecureTlsWarning", + out _suppressInsecureTlsWarningField, + out _suppressInsecureTlsWarningOriginal); + + InitField( + "s_makeReadAsyncBlocking", + out _makeReadAsyncBlockingField, + out _makeReadAsyncBlockingOriginal); + + InitField( + "s_useMinimumLoginTimeout", + out _useMinimumLoginTimeoutField, + out _useMinimumLoginTimeoutOriginal); + + InitField( + "s_legacyVarTimeZeroScaleBehaviour", + out _legacyVarTimeZeroScaleBehaviourField, + out _legacyVarTimeZeroScaleBehaviourOriginal); + + InitField( + "s_useConnectionPoolV2", + out _useConnectionPoolV2Field, + out _useConnectionPoolV2Original); + + #if NETFRAMEWORK + InitField( + "s_disableTnirByDefault", + out _disableTnirByDefaultField, + out _disableTnirByDefaultOriginal); + #endif + } + + /// + /// Disposal restores all original switch values as a best effort. + /// + /// + /// + /// Throws if any values could not be restored after trying to restore all + /// values. + /// + public void Dispose() + { + List failedFields = new(); + + void RestoreField(FieldInfo field, Tristate value) + { + try + { + SetValue(field, value); + } + catch (Exception) + { + failedFields.Add(field.Name); + } + } + + RestoreField( + _legacyRowVersionNullBehaviorField, + _legacyRowVersionNullBehaviorOriginal); + + RestoreField( + _suppressInsecureTlsWarningField, + _suppressInsecureTlsWarningOriginal); + + RestoreField( + _makeReadAsyncBlockingField, + _makeReadAsyncBlockingOriginal); + + RestoreField( + _useMinimumLoginTimeoutField, + _useMinimumLoginTimeoutOriginal); + + RestoreField( + _legacyVarTimeZeroScaleBehaviourField, + _legacyVarTimeZeroScaleBehaviourOriginal); + + RestoreField( + _useConnectionPoolV2Field, + _useConnectionPoolV2Original); + + #if NETFRAMEWORK + RestoreField( + _disableTnirByDefaultField, + _disableTnirByDefaultOriginal); + #endif + + if (failedFields.Count > 0) + { + throw new Exception( + "Failed to restore the following fields: " + + string.Join(", ", failedFields)); + } + } + + #endregion + + #region Public Properties + + /// + /// Access the LocalAppContextSwitches.LegacyRowVersionNullBehavior + /// property. + /// + public bool LegacyRowVersionNullBehavior + { + get => (bool)_legacyRowVersionNullBehaviorProperty.GetValue(null); + } + + /// + /// Access the LocalAppContextSwitches.SuppressInsecureTlsWarning property. + /// + public bool SuppressInsecureTlsWarning + { + get => (bool)_suppressInsecureTlsWarningProperty.GetValue(null); + } + + /// + /// Access the LocalAppContextSwitches.MakeReadAsyncBlocking property. + /// + public bool MakeReadAsyncBlocking + { + get => (bool)_makeReadAsyncBlockingProperty.GetValue(null); + } + + /// + /// Access the LocalAppContextSwitches.UseMinimumLoginTimeout property. + /// + public bool UseMinimumLoginTimeout + { + get => (bool)_useMinimumLoginTimeoutProperty.GetValue(null); + } + + /// + /// Access the LocalAppContextSwitches.LegacyVarTimeZeroScaleBehaviour + /// property. + /// + public bool LegacyVarTimeZeroScaleBehaviour + { + get => (bool)_legacyVarTimeZeroScaleBehaviourProperty.GetValue(null); + } + + /// + /// Access the LocalAppContextSwitches.UseConnectionPoolV2 property. + /// + public bool UseConnectionPoolV2 + { + get => (bool)_useConnectionPoolV2Property.GetValue(null); + } + + #if NETFRAMEWORK + /// + /// Access the LocalAppContextSwitches.DisableTnirByDefault property. + /// + public bool DisableTnirByDefault + { + get => (bool)_disableTnirByDefaultProperty.GetValue(null); + } + #endif + + // These properties get or set the like-named underlying switch field value. + // + // They all fail the test if the value cannot be retrieved or set. + + /// + /// Get or set the LocalAppContextSwitches.LegacyRowVersionNullBehavior + /// switch value. + /// + public Tristate LegacyRowVersionNullBehaviorField + { + get => GetValue(_legacyRowVersionNullBehaviorField); + set => SetValue(_legacyRowVersionNullBehaviorField, value); + } + + /// + /// Get or set the LocalAppContextSwitches.SuppressInsecureTlsWarning + /// switch value. + /// + public Tristate SuppressInsecureTlsWarningField + { + get => GetValue(_suppressInsecureTlsWarningField); + set => SetValue(_suppressInsecureTlsWarningField, value); + } + + /// + /// Get or set the LocalAppContextSwitches.MakeReadAsyncBlocking switch + /// value. + /// + public Tristate MakeReadAsyncBlockingField + { + get => GetValue(_makeReadAsyncBlockingField); + set => SetValue(_makeReadAsyncBlockingField, value); + } + + /// + /// Get or set the LocalAppContextSwitches.UseMinimumLoginTimeout switch + /// value. + /// + public Tristate UseMinimumLoginTimeoutField + { + get => GetValue(_useMinimumLoginTimeoutField); + set => SetValue(_useMinimumLoginTimeoutField, value); + } + + /// + /// Get or set the LocalAppContextSwitches.LegacyVarTimeZeroScaleBehaviour + /// switch value. + /// + public Tristate LegacyVarTimeZeroScaleBehaviourField + { + get => GetValue(_legacyVarTimeZeroScaleBehaviourField); + set => SetValue(_legacyVarTimeZeroScaleBehaviourField, value); + } + + /// + /// Get or set the LocalAppContextSwitches.UseConnectionPoolV2 switch value. + /// + public Tristate UseConnectionPoolV2Field + { + get => GetValue(_useConnectionPoolV2Field); + set => SetValue(_useConnectionPoolV2Field, value); + } + + #if NETFRAMEWORK + /// + /// Get or set the LocalAppContextSwitches.DisableTnirByDefault switch + /// value. + /// + public Tristate DisableTnirByDefaultField + { + get => GetValue(_disableTnirByDefaultField); + set => SetValue(_disableTnirByDefaultField, value); + } + #endif + + #endregion + + #region Private Helpers + + // Get the value of the given field, or throw if it is null. + private static Tristate GetValue(FieldInfo field) + { + var value = field.GetValue(null); + if (value is null) + { + throw new Exception($"Field {field.Name} has a null value."); + } + + return (Tristate)value; + } + + // Set the value of the given field. + private static void SetValue(FieldInfo field, Tristate value) + { + field.SetValue(null, (byte)value); + } + + #endregion +} diff --git a/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj b/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj index 8a076d1e52..1b35e8f80d 100644 --- a/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj +++ b/src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj @@ -13,6 +13,6 @@ - + diff --git a/src/Microsoft.Data.SqlClient/tests/Directory.Build.props b/src/Microsoft.Data.SqlClient/tests/Directory.Build.props index af31a2b584..b470f899e1 100644 --- a/src/Microsoft.Data.SqlClient/tests/Directory.Build.props +++ b/src/Microsoft.Data.SqlClient/tests/Directory.Build.props @@ -1,5 +1,5 @@ - @@ -14,10 +14,15 @@ Project + + + false + + net462 - net9.0 + net8.0;net9.0 diff --git a/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props b/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props new file mode 100644 index 0000000000..db17cb1e48 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Directory.Packages.props @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/Docker/Directory.Build.props b/src/Microsoft.Data.SqlClient/tests/Docker/Directory.Build.props deleted file mode 100644 index c2ea57281c..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/Docker/Directory.Build.props +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/src/Microsoft.Data.SqlClient/tests/Docker/DockerLinuxTest/Dockerfile b/src/Microsoft.Data.SqlClient/tests/Docker/DockerLinuxTest/Dockerfile deleted file mode 100644 index 4d48876e33..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/Docker/DockerLinuxTest/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. - -FROM mcr.microsoft.com/dotnet/runtime:8.0@sha256:113324d7d68c23885ee6ce61f192f5cd2bed7a42d80a2038f26538b5653a1250 AS base -WORKDIR /app - -FROM mcr.microsoft.com/dotnet/sdk:8.0.101@sha256:1b99a629b4f55a496db835d9c3347737aa4a1d995d43ffe5ab92d04bee69adeb AS build -WORKDIR /sqlclient -COPY . . - -ARG PROJNAME="Microsoft.Data.SqlClient.DockerLinuxTest" -ARG PROJFILE=$PROJNAME".csproj" -ARG DLLFILE=$PROJNAME".dll" - -WORKDIR /sqlclient/src/Microsoft.Data.SqlClient/tests/Docker/DockerLinuxTest -RUN dotnet build $PROJFILE -c Release -o /app/build - -FROM build AS publish -RUN dotnet publish $PROJFILE -c Release -o /app/publish - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . -ENTRYPOINT ["dotnet", $DLLFILE] diff --git a/src/Microsoft.Data.SqlClient/tests/Docker/DockerLinuxTest/Microsoft.Data.SqlClient.DockerLinuxTest.csproj b/src/Microsoft.Data.SqlClient/tests/Docker/DockerLinuxTest/Microsoft.Data.SqlClient.DockerLinuxTest.csproj deleted file mode 100644 index bd320959f7..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/Docker/DockerLinuxTest/Microsoft.Data.SqlClient.DockerLinuxTest.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - Exe - net8.0 - Linux - ..\..\..\..\.. - Unix - - - - - - - - diff --git a/src/Microsoft.Data.SqlClient/tests/Docker/DockerLinuxTest/Program.cs b/src/Microsoft.Data.SqlClient/tests/Docker/DockerLinuxTest/Program.cs deleted file mode 100644 index 527d6156ef..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/Docker/DockerLinuxTest/Program.cs +++ /dev/null @@ -1,27 +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; - -namespace Microsoft.Data.SqlClient.DockerLinuxTest -{ - class Program - { - static string server = "microsoft.sqlserver"; - static string user = "sa"; - // Provide password as set in docker-compose.yml - static string pwd = "P@ssw0rd!123"; - - static void Main(string[] args) - { - using (SqlConnection sqlConnection = new SqlConnection($"Server={server}; UID={user}; PWD={pwd}")) - { - sqlConnection.Open(); - Console.WriteLine($"Connected to SQL Server v{sqlConnection.ServerVersion} from {Environment.OSVersion.VersionString}"); - // Write your code here to debug inside Docker Linux containers. - } - } - } -} - diff --git a/src/Microsoft.Data.SqlClient/tests/Docker/DockerLinuxTest/Properties/launchSettings.json b/src/Microsoft.Data.SqlClient/tests/Docker/DockerLinuxTest/Properties/launchSettings.json deleted file mode 100644 index 008eabbd78..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/Docker/DockerLinuxTest/Properties/launchSettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "profiles": { - "Microsoft.Data.SqlClient.DockerLinuxTest": { - "commandName": "Project" - }, - "Docker": { - "commandName": "Docker" - } - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs index 18997a4d86..e97b251613 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AADAuthenticationTests.cs @@ -53,7 +53,7 @@ private void InvalidCombinationCheck(SqlCredential credential) [Fact] public void CustomActiveDirectoryProviderTest() { - SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(CustomDeviceFlowCallback); + SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(static (result) => Task.CompletedTask); SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); Assert.Equal(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); } @@ -69,15 +69,9 @@ public void CustomActiveDirectoryProviderTest_AppClientId() [Fact] public void CustomActiveDirectoryProviderTest_AppClientId_DeviceFlowCallback() { - SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(CustomDeviceFlowCallback, Guid.NewGuid().ToString()); + SqlAuthenticationProvider authProvider = new ActiveDirectoryAuthenticationProvider(static (result) => Task.CompletedTask, Guid.NewGuid().ToString()); SqlAuthenticationProvider.SetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow, authProvider); Assert.Equal(authProvider, SqlAuthenticationProvider.GetProvider(SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)); } - - private Task CustomDeviceFlowCallback(DeviceCodeResult result) - { - Console.WriteLine(result.Message); - return Task.FromResult(0); - } } } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs index e2d8e02b0b..caa98cc686 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsAlgorithmErrors.cs @@ -7,18 +7,38 @@ using System.Reflection; using System.Security.Cryptography.X509Certificates; using System.Text; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures; using Xunit; using static Microsoft.Data.SqlClient.Tests.AlwaysEncryptedTests.Utility; namespace Microsoft.Data.SqlClient.Tests.AlwaysEncryptedTests { - public class ExceptionsAlgorithmErrors : IClassFixture + public class ExceptionsAlgorithmErrors : IClassFixture { // Reflection public static Assembly systemData = Assembly.GetAssembly(typeof(SqlConnection)); public static Type sqlClientSymmetricKey = systemData.GetType("Microsoft.Data.SqlClient.SqlClientSymmetricKey"); public static ConstructorInfo sqlColumnEncryptionKeyConstructor = sqlClientSymmetricKey.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(byte[]) }, null); + private readonly ColumnEncryptionCertificateFixture _fixture; + private readonly byte[] _cek; + private readonly byte[] _encryptedCek; + private readonly string _certificatePath; + + public ExceptionsAlgorithmErrors(ColumnEncryptionCertificateFixture fixture) + { + // Disable the cache to avoid false failures. + SqlConnection.ColumnEncryptionQueryMetadataCacheEnabled = false; + + SqlColumnEncryptionCertificateStoreProvider provider = new SqlColumnEncryptionCertificateStoreProvider(); + X509Certificate2 currUserCertificate = fixture.GetCertificate(StoreLocation.CurrentUser); + + _cek = GenerateRandomBytes(32); + _fixture = fixture; + _certificatePath = string.Format("CurrentUser/My/{0}", currUserCertificate.Thumbprint); + _encryptedCek = provider.EncryptColumnEncryptionKey(_certificatePath, "RSA_OAEP", _cek); + } + [Fact] [PlatformSpecific(TestPlatforms.Windows)] public void TestNullCEK() @@ -52,9 +72,9 @@ public void TestInvalidEncryptionType() { const byte invalidEncryptionType = 3; Object cipherMD = GetSqlCipherMetadata(0, 2, null, invalidEncryptionType, 0x01); - AddEncryptionKeyToCipherMD(cipherMD, CertFixture.encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, CertFixture.certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); + AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); - byte[] cipherText = EncryptDataUsingAED(plainText, CertFixture.cek, CColumnEncryptionType.Deterministic); + byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic); string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_InvalidEncryptionType, "AEAD_AES_256_CBC_HMAC_SHA256", invalidEncryptionType, "'Deterministic', 'Randomized'"); @@ -74,7 +94,7 @@ public void TestInvalidCipherText() string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_InvalidCipherTextSize, invalidCiphertextLength, 65); byte[] cipherText = GenerateRandomBytes(invalidCiphertextLength); // minimum length is 65 - TargetInvocationException e = Assert.Throws(() => DecryptDataUsingAED(cipherText, CertFixture.cek, CColumnEncryptionType.Deterministic)); + TargetInvocationException e = Assert.Throws(() => DecryptDataUsingAED(cipherText, _cek, CColumnEncryptionType.Deterministic)); Assert.Contains(expectedMessage, e.InnerException.Message); } @@ -85,10 +105,10 @@ public void TestInvalidAlgorithmVersion() string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_InvalidAlgorithmVersion, 40, "01"); byte[] plainText = Encoding.Unicode.GetBytes("Hello World"); - byte[] cipherText = EncryptDataUsingAED(plainText, CertFixture.cek, CColumnEncryptionType.Deterministic); + byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic); // Put a version number of 0x10 cipherText[0] = 0x40; - TargetInvocationException e = Assert.Throws(() => DecryptDataUsingAED(cipherText, CertFixture.cek, CColumnEncryptionType.Deterministic)); + TargetInvocationException e = Assert.Throws(() => DecryptDataUsingAED(cipherText, _cek, CColumnEncryptionType.Deterministic)); Assert.Contains(expectedMessage, e.InnerException.Message); } @@ -98,13 +118,13 @@ public void TestInvalidAuthenticationTag() { string expectedMessage = SystemDataResourceManager.Instance.TCE_InvalidAuthenticationTag; byte[] plainText = Encoding.Unicode.GetBytes("Hello World"); - byte[] cipherText = EncryptDataUsingAED(plainText, CertFixture.cek, CColumnEncryptionType.Deterministic); + byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic); // Zero out 4 bytes of authentication tag for (int i = 0; i < 4; i++) { cipherText[i + 1] = 0x00; } - TargetInvocationException e = Assert.Throws(() => DecryptDataUsingAED(cipherText, CertFixture.cek, CColumnEncryptionType.Deterministic)); + TargetInvocationException e = Assert.Throws(() => DecryptDataUsingAED(cipherText, _cek, CColumnEncryptionType.Deterministic)); Assert.Contains(expectedMessage, e.InnerException.Message); } @@ -115,9 +135,9 @@ public void TestNullColumnEncryptionAlgorithm() string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_NullColumnEncryptionAlgorithm, "'AEAD_AES_256_CBC_HMAC_SHA256'"); Object cipherMD = GetSqlCipherMetadata(0, 0, null, 1, 0x01); - AddEncryptionKeyToCipherMD(cipherMD, CertFixture.encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, CertFixture.certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); + AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); - byte[] cipherText = EncryptDataUsingAED(plainText, CertFixture.cek, CColumnEncryptionType.Deterministic); + byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic); TargetInvocationException e = Assert.Throws(() => DecryptWithKey(cipherText, cipherMD)); Assert.Contains(expectedMessage, e.InnerException.Message); @@ -133,9 +153,9 @@ public void TestUnknownEncryptionAlgorithmId() string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_UnknownColumnEncryptionAlgorithmId, unknownEncryptionAlgoId, "'1', '2'"); Object cipherMD = GetSqlCipherMetadata(0, unknownEncryptionAlgoId, null, 1, 0x01); - AddEncryptionKeyToCipherMD(cipherMD, CertFixture.encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, CertFixture.certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); + AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); - byte[] cipherText = EncryptDataUsingAED(plainText, CertFixture.cek, CColumnEncryptionType.Deterministic); + byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic); Exception decryptEx = Assert.Throws(() => DecryptWithKey(plainText, cipherMD)); Assert.Matches(expectedMessage, decryptEx.InnerException.Message); @@ -157,9 +177,9 @@ public void TestUnknownCustomKeyStoreProvider() string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_UnrecognizedKeyStoreProviderName, invalidProviderName, "'MSSQL_CERTIFICATE_STORE', 'MSSQL_CNG_STORE', 'MSSQL_CSP_PROVIDER'", ""); Object cipherMD = GetSqlCipherMetadata(0, 1, null, 1, 0x03); - AddEncryptionKeyToCipherMD(cipherMD, CertFixture.encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, CertFixture.certificatePath, invalidProviderName, "RSA_OAEP"); + AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, invalidProviderName, "RSA_OAEP"); byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); - byte[] cipherText = EncryptDataUsingAED(plainText, CertFixture.cek, CColumnEncryptionType.Deterministic); + byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic); Exception decryptEx = Assert.Throws(() => DecryptWithKey(plainText, cipherMD)); Assert.Contains(expectedMessage, decryptEx.InnerException.Message); @@ -179,9 +199,9 @@ public void TestTceUnknownEncryptionAlgorithm() string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_UnknownColumnEncryptionAlgorithm, unknownEncryptionAlgorithm, "'AEAD_AES_256_CBC_HMAC_SHA256'"); Object cipherMD = GetSqlCipherMetadata(0, 0, "Dummy", 1, 0x01); - AddEncryptionKeyToCipherMD(cipherMD, CertFixture.encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, CertFixture.certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); + AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); - byte[] cipherText = EncryptDataUsingAED(plainText, CertFixture.cek, CColumnEncryptionType.Deterministic); + byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic); Exception decryptEx = Assert.Throws(() => DecryptWithKey(cipherText, cipherMD)); Assert.Contains(expectedMessage, decryptEx.InnerException.Message); @@ -194,15 +214,15 @@ public void TestTceUnknownEncryptionAlgorithm() [PlatformSpecific(TestPlatforms.Windows)] public void TestExceptionsFromCertStore() { - byte[] corruptedCek = GenerateInvalidEncryptedCek(CertFixture.cek, ECEKCorruption.SIGNATURE); + byte[] corruptedCek = GenerateInvalidEncryptedCek(_cek, ECEKCorruption.SIGNATURE); string expectedMessage = string.Format(SystemDataResourceManager.Instance.TCE_KeyDecryptionFailedCertStore, "MSSQL_CERTIFICATE_STORE", BitConverter.ToString(corruptedCek, corruptedCek.Length - 10, 10)); Object cipherMD = GetSqlCipherMetadata(0, 1, null, 1, 0x01); - AddEncryptionKeyToCipherMD(cipherMD, corruptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, CertFixture.certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); + AddEncryptionKeyToCipherMD(cipherMD, corruptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "MSSQL_CERTIFICATE_STORE", "RSA_OAEP"); byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); - byte[] cipherText = EncryptDataUsingAED(plainText, CertFixture.cek, CColumnEncryptionType.Deterministic); + byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic); Exception decryptEx = Assert.Throws(() => DecryptWithKey(cipherText, cipherMD)); Assert.Matches(expectedMessage, decryptEx.InnerException.Message); @@ -224,9 +244,9 @@ public void TestExceptionsFromCustomKeyStore() SqlConnection.RegisterColumnEncryptionKeyStoreProviders(customProviders); object cipherMD = GetSqlCipherMetadata(0, 1, null, 1, 0x01); - AddEncryptionKeyToCipherMD(cipherMD, CertFixture.encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, CertFixture.certificatePath, "DummyProvider", "DummyAlgo"); + AddEncryptionKeyToCipherMD(cipherMD, _encryptedCek, 0, 0, 0, new byte[] { 0x01, 0x02, 0x03 }, _certificatePath, "DummyProvider", "DummyAlgo"); byte[] plainText = Encoding.Unicode.GetBytes("HelloWorld"); - byte[] cipherText = EncryptDataUsingAED(plainText, CertFixture.cek, CColumnEncryptionType.Deterministic); + byte[] cipherText = EncryptDataUsingAED(plainText, _cek, CColumnEncryptionType.Deterministic); Exception decryptEx = Assert.Throws(() => DecryptWithKey(cipherText, cipherMD)); Assert.Contains(expectedMessage, decryptEx.InnerException.Message); @@ -238,35 +258,4 @@ public void TestExceptionsFromCustomKeyStore() } } } - - public class CertFixture : IDisposable - { - private readonly SqlColumnEncryptionCertificateStoreProvider provider = new SqlColumnEncryptionCertificateStoreProvider(); - - public static X509Certificate2 certificate; - public static string thumbprint; - public static string certificatePath; - public static byte[] cek; - public static byte[] encryptedCek; - - public CertFixture() - { - if (certificate == null) - { - certificate = Utility.CreateCertificate(); - } - thumbprint = certificate.Thumbprint; - certificatePath = string.Format("CurrentUser/My/{0}", thumbprint); - cek = GenerateRandomBytes(32); - encryptedCek = provider.EncryptColumnEncryptionKey(certificatePath, "RSA_OAEP", cek); - - // Disable the cache to avoid false failures. - SqlConnection.ColumnEncryptionQueryMetadataCacheEnabled = false; - } - - public void Dispose() - { - // Do NOT remove certificate for concurrent consistency. Certificates are used for other test cases as well. - } - } } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsCertStore.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsCertStore.cs deleted file mode 100644 index 30f4528d23..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/ExceptionsCertStore.cs +++ /dev/null @@ -1,104 +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.Security.Cryptography.X509Certificates; -using Xunit; - -namespace Microsoft.Data.SqlClient.Tests.AlwaysEncryptedTests -{ - public class ExceptionsCertStore : IClassFixture - { - private readonly string masterKeyEncAlgo = "RSA_OAEP"; - - [Fact] - [PlatformSpecific(TestPlatforms.Windows)] - public void EmptyCertificateThumbprint() - { - string dummyPath = string.Format("CurrentUser/My/"); - string expectedMessage = string.Format(@"Empty certificate thumbprint specified in certificate path '{0}'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?", dummyPath); - - ArgumentException e = Assert.Throws(() => ExceptionCertFixture.certStoreProvider.EncryptColumnEncryptionKey(dummyPath, masterKeyEncAlgo, ExceptionCertFixture.encryptedCek)); - Assert.Matches(expectedMessage, e.Message); - - expectedMessage = string.Format(@"Internal error. Empty certificate thumbprint specified in certificate path '{0}'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?", dummyPath); - e = Assert.Throws(() => ExceptionCertFixture.certStoreProvider.DecryptColumnEncryptionKey(dummyPath, masterKeyEncAlgo, ExceptionCertFixture.encryptedCek)); - Assert.Matches(expectedMessage, e.Message); - } - - [Fact] - [PlatformSpecific(TestPlatforms.Windows)] - public void CertificateNotFound() - { - string dummyPath = string.Format("CurrentUser/My/JunkThumbprint"); - string expectedMessage = string.Format(@"Certificate with thumbprint 'JunkThumbprint' not found in certificate store 'My' in certificate location 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"); - ArgumentException e = Assert.Throws(() => ExceptionCertFixture.certStoreProvider.EncryptColumnEncryptionKey(dummyPath, masterKeyEncAlgo, ExceptionCertFixture.encryptedCek)); - Assert.Matches(expectedMessage, e.Message); - - expectedMessage = string.Format(@"Certificate with thumbprint 'JunkThumbprint' not found in certificate store 'My' in certificate location 'CurrentUser'. Verify the certificate path in the column master key definition in the database is correct, and the certificate has been imported correctly into the certificate location/store.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"); - e = Assert.Throws(() => ExceptionCertFixture.certStoreProvider.DecryptColumnEncryptionKey(dummyPath, masterKeyEncAlgo, ExceptionCertFixture.encryptedCek)); - Assert.Matches(expectedMessage, e.Message); - } - -#if NETFRAMEWORK - [Fact] - [SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp)] - public void CertificateWithNoPrivateKey() - { - string expectedMessage = string.Format("Certificate specified in key path '{0}' does not have a private key to encrypt a column encryption key. Verify the certificate is imported correctly.\r\nParameter name: masterKeyPath", ExceptionCertFixture.masterKeyPathNPK); - ArgumentException e = Assert.Throws(() => - ExceptionCertFixture.certStoreProvider.EncryptColumnEncryptionKey( - ExceptionCertFixture.masterKeyPathNPK, masterKeyEncAlgo, ExceptionCertFixture.encryptedCek)); - Assert.Contains(expectedMessage, e.Message); - - expectedMessage = string.Format("Certificate specified in key path '{0}' does not have a private key to decrypt a column encryption key. Verify the certificate is imported correctly.\r\nParameter name: masterKeyPath", ExceptionCertFixture.masterKeyPathNPK); - e = Assert.Throws(() => - ExceptionCertFixture.certStoreProvider.DecryptColumnEncryptionKey( - ExceptionCertFixture.masterKeyPathNPK, masterKeyEncAlgo, ExceptionCertFixture.encryptedCek)); - Assert.Contains(expectedMessage, e.Message); - } -#endif - } - public class ExceptionCertFixture : IDisposable - { - public static readonly SqlColumnEncryptionCertificateStoreProvider certStoreProvider = new SqlColumnEncryptionCertificateStoreProvider(); - public static X509Certificate2 certificate; - public static string certificatePath; - public static string thumbprint; - public static byte[] cek; - public static byte[] encryptedCek; -#if NETFRAMEWORK - public static X509Certificate2 masterKeyCertificateNPK; // no private key - public static string thumbprintNPK; // No private key - public static string masterKeyPathNPK; -#endif - - public ExceptionCertFixture() - { - if(certificate == null) - { - certificate = Utility.CreateCertificate(); - } - thumbprint = certificate.Thumbprint; - certificatePath = string.Format("CurrentUser/My/{0}", thumbprint); - cek = Utility.GenerateRandomBytes(32); - encryptedCek = certStoreProvider.EncryptColumnEncryptionKey(certificatePath, "RSA_OAEP", cek); -#if NETFRAMEWORK - if (masterKeyCertificateNPK == null) - { - masterKeyCertificateNPK = Utility.CreateCertificateWithNoPrivateKey(); - } - thumbprintNPK = masterKeyCertificateNPK.Thumbprint; - masterKeyPathNPK = "CurrentUser/My/" + thumbprintNPK; -#endif - // Disable the cache to avoid false failures. - SqlConnection.ColumnEncryptionQueryMetadataCacheEnabled = false; - } - - public void Dispose() - { - // Do NOT remove certificate for concurrent consistency. Certificates are used for other test cases as well. - } - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCertificateStoreProviderShould.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCertificateStoreProviderShould.cs index c6107a1852..e1fe6a0a09 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCertificateStoreProviderShould.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/SqlColumnEncryptionCertificateStoreProviderShould.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using Microsoft.Data.SqlClient.Tests.Common.Fixtures; using System; using System.Collections.Generic; using System.Linq; @@ -16,9 +17,10 @@ namespace Microsoft.Data.SqlClient.Tests.AlwaysEncryptedTests { - public class SqlColumnEncryptionCertificateStoreProviderWindowsShould : IClassFixture + public class SqlColumnEncryptionCertificateStoreProviderWindowsShould : IClassFixture { - private const string MASTER_KEY_PATH = "CurrentUser/My/C74D53B816A971E3FF9714FE1DD2E57E1710D946"; + private const string PRIMARY_CERTIFICATE_PATH = "CurrentUser/My/{primary_thumbprint}"; + private const string SECONDARY_CERTIFICATE_PATH = "CurrentUser/My/{secondary_thumbprint}"; private const string ENCRYPTION_ALGORITHM = "RSA_OAEP"; /// @@ -93,14 +95,25 @@ public class SqlColumnEncryptionCertificateStoreProviderWindowsShould : IClassFi /// private const int CipherTextStartIndex = IVStartIndex + IVLengthInBytes; + private readonly ColumnEncryptionCertificateFixture _certFixture; + + public SqlColumnEncryptionCertificateStoreProviderWindowsShould(ColumnEncryptionCertificateFixture certFixture) + { + _certFixture = certFixture; + + // Disable the cache to avoid false failures. + SqlConnection.ColumnEncryptionQueryMetadataCacheEnabled = false; + } + [Theory] [InvalidDecryptionParameters] [PlatformSpecific(TestPlatforms.Windows)] public void ThrowExceptionWithInvalidParameterWhileDecryptingColumnEncryptionKey(string errorMsg, Type exceptionType, string masterKeyPath, string encryptionAlgorithm, byte[] bytes) { var provider = new SqlColumnEncryptionCertificateStoreProvider(); - Exception ex = Assert.Throws(exceptionType, () => provider.DecryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm, bytes)); - Assert.Matches(errorMsg, ex.Message); + Exception ex = Assert.Throws(exceptionType, + () => provider.DecryptColumnEncryptionKey(ReplaceKeyTokens(masterKeyPath), encryptionAlgorithm, bytes)); + Assert.Matches(ReplaceKeyTokens(errorMsg), ex.Message); } [Theory] @@ -109,8 +122,8 @@ public void ThrowExceptionWithInvalidParameterWhileDecryptingColumnEncryptionKey public void ThrowExceptionWithInvalidParameterWhileEncryptingColumnEncryptionKey(string errorMsg, Type exceptionType, string masterKeyPath, string encryptionAlgorithm, byte[] bytes) { var provider = new SqlColumnEncryptionCertificateStoreProvider(); - Exception ex = Assert.Throws(exceptionType, () => provider.EncryptColumnEncryptionKey(masterKeyPath, encryptionAlgorithm, bytes)); - Assert.Matches(errorMsg, ex.Message); + Exception ex = Assert.Throws(exceptionType, () => provider.EncryptColumnEncryptionKey(ReplaceKeyTokens(masterKeyPath), encryptionAlgorithm, bytes)); + Assert.Matches(ReplaceKeyTokens(errorMsg), ex.Message); } [Theory] @@ -124,26 +137,26 @@ public void ThrowExceptionWithInvalidParameterWhileSigningColumnMasterKeyMetadat } [Theory] - [InlineData("CurrentUser/My/C74D53B816A971E3FF9714FE1DD2E57E1710D946")] - [InlineData("CURRENTUSER/My/C74D53B816A971E3FF9714FE1DD2E57E1710D946")] - [InlineData("currentuser/My/C74D53B816A971E3FF9714FE1DD2E57E1710D946")] + [InlineData("CurrentUser/My/{primary_thumbprint}")] + [InlineData("CURRENTUSER/My/{primary_thumbprint}")] + [InlineData("currentuser/My/{primary_thumbprint}")] [PlatformSpecific(TestPlatforms.Windows)] - public void SetStoreLocationApproperiatelyFromMasterKeyPathRegardlessOfCase(string masterKeyPath) + public void SetStoreLocationAppropriatelyFromMasterKeyPathRegardlessOfCase(string masterKeyPath) { var provider = new SqlColumnEncryptionCertificateStoreProvider(); - byte[] ciphertext = provider.EncryptColumnEncryptionKey(masterKeyPath, ENCRYPTION_ALGORITHM, new byte[] { 1, 2, 3, 4, 5 }); + byte[] ciphertext = provider.EncryptColumnEncryptionKey(ReplaceKeyTokens(masterKeyPath), ENCRYPTION_ALGORITHM, new byte[] { 1, 2, 3, 4, 5 }); Assert.NotNull(ciphertext); } [Theory] - [InlineData("CurrentUser/my/C74D53B816A971E3FF9714FE1DD2E57E1710D946")] - [InlineData("CurrentUser/MY/C74D53B816A971E3FF9714FE1DD2E57E1710D946")] - [InlineData("CurrentUser/My/C74D53B816A971E3FF9714FE1DD2E57E1710D946")] + [InlineData("CurrentUser/my/{primary_thumbprint}")] + [InlineData("CurrentUser/MY/{primary_thumbprint}")] + [InlineData("CurrentUser/My/{primary_thumbprint}")] [PlatformSpecific(TestPlatforms.Windows)] - public void SetStoreNameApproperiatelyFromMasterKeyPathRegardlessOfCase(string masterKeyPath) + public void SetStoreNameAppropriatelyFromMasterKeyPathRegardlessOfCase(string masterKeyPath) { var provider = new SqlColumnEncryptionCertificateStoreProvider(); - byte[] ciphertext = provider.EncryptColumnEncryptionKey(masterKeyPath, ENCRYPTION_ALGORITHM, new byte[] { 1, 2, 3, 4, 5 }); + byte[] ciphertext = provider.EncryptColumnEncryptionKey(ReplaceKeyTokens(masterKeyPath), ENCRYPTION_ALGORITHM, new byte[] { 1, 2, 3, 4, 5 }); Assert.NotNull(ciphertext); } @@ -155,7 +168,7 @@ public void SetStoreNameApproperiatelyFromMasterKeyPathRegardlessOfCase(string m public void AcceptEncryptionAlgorithmRegardlessOfCase(string algorithm) { var provider = new SqlColumnEncryptionCertificateStoreProvider(); - byte[] ciphertext = provider.EncryptColumnEncryptionKey(MASTER_KEY_PATH, algorithm, new byte[] { 1, 2, 3, 4, 5 }); + byte[] ciphertext = provider.EncryptColumnEncryptionKey(ReplaceKeyTokens(PRIMARY_CERTIFICATE_PATH), algorithm, new byte[] { 1, 2, 3, 4, 5 }); Assert.NotNull(ciphertext); } @@ -171,8 +184,8 @@ public void EncryptKeyAndThenDecryptItSuccessfully(int dataSize) var randomNumberGenerator = new Random(); randomNumberGenerator.NextBytes(columnEncryptionKey); - byte[] encryptedData = provider.EncryptColumnEncryptionKey(MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, columnEncryptionKey); - byte[] decryptedData = provider.DecryptColumnEncryptionKey(MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, encryptedData); + byte[] encryptedData = provider.EncryptColumnEncryptionKey(ReplaceKeyTokens(PRIMARY_CERTIFICATE_PATH), ENCRYPTION_ALGORITHM, columnEncryptionKey); + byte[] decryptedData = provider.DecryptColumnEncryptionKey(ReplaceKeyTokens(PRIMARY_CERTIFICATE_PATH), ENCRYPTION_ALGORITHM, encryptedData); Assert.Equal(columnEncryptionKey, decryptedData); } @@ -183,10 +196,10 @@ public void EncryptKeyAndThenDecryptItSuccessfully(int dataSize) public void SignAndVerifyColumnMasterKeyMetadataSuccessfully(bool allowEnclaveComputations) { var provider = new SqlColumnEncryptionCertificateStoreProvider(); - byte[] signature = provider.SignColumnMasterKeyMetadata(MASTER_KEY_PATH, allowEnclaveComputations); + byte[] signature = provider.SignColumnMasterKeyMetadata(ReplaceKeyTokens(PRIMARY_CERTIFICATE_PATH), allowEnclaveComputations); Assert.NotNull(signature); - Assert.True(provider.VerifyColumnMasterKeyMetadata(MASTER_KEY_PATH, allowEnclaveComputations, signature)); - Assert.False(provider.VerifyColumnMasterKeyMetadata(MASTER_KEY_PATH, !allowEnclaveComputations, signature)); + Assert.True(provider.VerifyColumnMasterKeyMetadata(ReplaceKeyTokens(PRIMARY_CERTIFICATE_PATH), allowEnclaveComputations, signature)); + Assert.False(provider.VerifyColumnMasterKeyMetadata(ReplaceKeyTokens(PRIMARY_CERTIFICATE_PATH), !allowEnclaveComputations, signature)); } [Theory] @@ -197,10 +210,10 @@ public void FailToVerifyColumnMasterKeyMetadataWithWrongCertificate(bool allowEn { var provider = new SqlColumnEncryptionCertificateStoreProvider(); - byte[] signature = provider.SignColumnMasterKeyMetadata(MASTER_KEY_PATH, allowEnclaveComputations); + byte[] signature = provider.SignColumnMasterKeyMetadata(ReplaceKeyTokens(PRIMARY_CERTIFICATE_PATH), allowEnclaveComputations); Assert.NotNull(signature); Assert.False( - provider.VerifyColumnMasterKeyMetadata("CurrentUser/My/4281446463C6F7F5B8EDFFA4BD6E345E46857CAD", allowEnclaveComputations, signature)); + provider.VerifyColumnMasterKeyMetadata(ReplaceKeyTokens(SECONDARY_CERTIFICATE_PATH), allowEnclaveComputations, signature)); } [Fact] @@ -209,24 +222,31 @@ public void EncryptAndDecryptDataSuccessfully() { var input = new byte[] { 1, 2, 3, 4, 5 }; var provider = new SqlColumnEncryptionCertificateStoreProvider(); - byte[] ciphertext = provider.EncryptColumnEncryptionKey(MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, + byte[] ciphertext = provider.EncryptColumnEncryptionKey(ReplaceKeyTokens(PRIMARY_CERTIFICATE_PATH), ENCRYPTION_ALGORITHM, new byte[] { 1, 2, 3, 4, 5 }); byte[] output = - provider.DecryptColumnEncryptionKey(MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, ciphertext); + provider.DecryptColumnEncryptionKey(ReplaceKeyTokens(PRIMARY_CERTIFICATE_PATH), ENCRYPTION_ALGORITHM, ciphertext); Assert.Equal(input, output); } [Theory] - [CEKEncryptionReversalParameters] + [MemberData( + nameof(CEKEncryptionReversalData) +#if NETFRAMEWORK + // .NET Framework puts system enums in something called the Global + // Assembly Cache (GAC), and xUnit refuses to serialize enums that + // live there. So for .NET Framework, we disable enumeration of the + // test data to avoid warnings on the console when running tests. + , DisableDiscoveryEnumeration = true +#endif + )] [PlatformSpecific(TestPlatforms.Windows)] public void TestCEKEncryptionReversal(StoreLocation certificateStoreLocation, String certificateStoreNameAndLocation) { Assert.True(!string.IsNullOrWhiteSpace(certificateStoreNameAndLocation)); - string certificateName = @"TestCertificate12"; // Fetch the newly created cert. - X509Certificate2 masterKeyCertificate = Utility.GetCertificate(certificateName, - certificateStoreLocation); + X509Certificate2 masterKeyCertificate = _certFixture.GetCertificate(certificateStoreLocation); Assert.True(masterKeyCertificate != null); @@ -370,8 +390,13 @@ public void TestCustomKeyProviderListSetter() } [Theory] + [MemberData( + nameof(ValidCertificatePathsData) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] [PlatformSpecific(TestPlatforms.Windows)] - [ValidCertificatePathsParameters] public void TestValidCertificatePaths(string certificateStoreNameAndLocation, object location) { StoreLocation certificateStoreLocation; @@ -389,11 +414,8 @@ public void TestValidCertificatePaths(string certificateStoreNameAndLocation, ob certificateStoreLocation = StoreLocation.CurrentUser; } - string certificateName = @"TestCertificate12"; - // Fetch the newly created cert. - X509Certificate2 masterKeyCertificate = Utility.GetCertificate(certificateName, - certificateStoreLocation); + X509Certificate2 masterKeyCertificate = _certFixture.GetCertificate(certificateStoreLocation); Assert.True(masterKeyCertificate != null); @@ -459,6 +481,13 @@ public void TestEncryptedCellValueTampering(string parameterToTamper, Utility.CC Assert.Matches(expectedErrorMessage, e.InnerException.Message); } + private string ReplaceKeyTokens(string keyPath) + { + return keyPath?.Replace("{primary_thumbprint}", _certFixture.PrimaryColumnEncryptionCertificate.Thumbprint) + ?.Replace("{secondary_thumbprint}", _certFixture.SecondaryColumnEncryptionCertificate.Thumbprint) + ?.Replace("{npk_thumbprint}", _certFixture.CertificateWithoutPrivateKey.Thumbprint); + } + public class AeadEncryptionParameters : DataAttribute { /// @@ -490,33 +519,25 @@ public override IEnumerable GetData(MethodInfo testMethod) } } - public class CEKEncryptionReversalParameters : DataAttribute + public static IEnumerable CEKEncryptionReversalData() { - public override IEnumerable GetData(MethodInfo testMethod) + yield return new object[2] { StoreLocation.CurrentUser, CurrentUserMyPathPrefix }; + // use localmachine cert path only when current user is Admin. + if (ColumnEncryptionCertificateFixture.IsAdmin) { - yield return new object[2] { StoreLocation.CurrentUser, CurrentUserMyPathPrefix }; - // use localmachine cert path only when current user is Admin. - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && CertificateFixture.IsAdmin) - { - yield return new object[2] { StoreLocation.LocalMachine, LocalMachineMyPathPrefix }; - } + yield return new object[2] { StoreLocation.LocalMachine, LocalMachineMyPathPrefix }; } } - - public class ValidCertificatePathsParameters : DataAttribute + public static IEnumerable ValidCertificatePathsData() { - - public override IEnumerable GetData(MethodInfo testMethod) + yield return new object[2] { CurrentUserMyPathPrefix, StoreLocation.CurrentUser }; + // use localmachine cert path (or an incomplete path, which defaults to localmachine) only when current user is Admin. + if (ColumnEncryptionCertificateFixture.IsAdmin) { - yield return new object[2] { CurrentUserMyPathPrefix, StoreLocation.CurrentUser }; - yield return new object[2] { MyPathPrefix, null }; - yield return new object[2] { @"", null }; - // use localmachine cert path only when current user is Admin. - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && CertificateFixture.IsAdmin) - { - yield return new object[2] { LocalMachineMyPathPrefix, StoreLocation.LocalMachine }; - } + yield return new object[2] { MyPathPrefix, StoreLocation.LocalMachine }; + yield return new object[2] { @"", StoreLocation.LocalMachine }; + yield return new object[2] { LocalMachineMyPathPrefix, StoreLocation.LocalMachine }; } } @@ -532,30 +553,33 @@ public class InvalidDecryptionParameters : DataAttribute private const string TCE_InvalidCertificatePath = @"Internal error. Invalid certificate path: 'CurrentUser/My/Thumbprint/extra'. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; private const string TCE_InvalidCertificateLocation = @"Internal error. Invalid certificate location 'Invalid' in certificate path 'Invalid/My/Thumbprint'. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; private const string TCE_InvalidCertificateStore = @"Internal error. Invalid certificate store 'Invalid' specified in certificate path 'CurrentUser/Invalid/Thumbprint'. Expected value: 'My'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + private const string TCE_CertificateNotFound = @"Certificate with thumbprint 'JunkThumbprint' not found in certificate store 'My' in certificate location 'CurrentUser'. Verify the certificate path in the column master key definition in the database is correct, and the certificate has been imported correctly into the certificate location/store.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + private const string TCE_CertificateWithNoPrivateKey = @"Certificate specified in key path 'CurrentUser/My/{npk_thumbprint}' does not have a private key to decrypt a column encryption key. Verify the certificate is imported correctly.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; private const string TCE_InvalidCertificateSignature = @"Internal error. Empty certificate thumbprint specified in certificate path 'CurrentUser/My/'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; private const string TCE_InvalidAlgorithmVersion = @"Specified encrypted column encryption key contains an invalid encryption algorithm version '02'. Expected version is '01'.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - private const string TCE_InvalidCiphertextLengthInEncryptedCEK = @"The specified encrypted column encryption key's ciphertext length: 128 does not match the ciphertext length: 256 when using column master key \(certificate\) in 'CurrentUser/My/C74D53B816A971E3FF9714FE1DD2E57E1710D946'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - private const string TCE_InvalidSignatureInEncryptedCEK = @"The specified encrypted column encryption key's signature length: 128 does not match the signature length: 256 when using column master key \(certificate\) in 'CurrentUser/My/C74D53B816A971E3FF9714FE1DD2E57E1710D946'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - private const string TCE_InvalidSignature = @"The specified encrypted column encryption key signature does not match the signature computed with the column master key \(certificate\) in 'CurrentUser/My/C74D53B816A971E3FF9714FE1DD2E57E1710D946'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - + private const string TCE_InvalidCiphertextLengthInEncryptedCEK = @"The specified encrypted column encryption key's ciphertext length: 128 does not match the ciphertext length: 256 when using column master key \(certificate\) in 'CurrentUser/My/{primary_thumbprint}'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + private const string TCE_InvalidSignatureInEncryptedCEK = @"The specified encrypted column encryption key's signature length: 128 does not match the signature length: 256 when using column master key \(certificate\) in 'CurrentUser/My/{primary_thumbprint}'. The encrypted column encryption key may be corrupt, or the specified certificate path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + private const string TCE_InvalidSignature = @"The specified encrypted column encryption key signature does not match the signature computed with the column master key \(certificate\) in 'CurrentUser/My/{primary_thumbprint}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; public override IEnumerable GetData(MethodInfo testMethod) { yield return new Object[] { TCE_NullCertificatePath, typeof(ArgumentNullException), null, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; yield return new Object[] { TCE_EmptyCertificatePath, typeof(ArgumentException), "", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_NullEncryptedColumnEncryptionKey, typeof(ArgumentNullException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, null }; - yield return new Object[] { TCE_EmptyEncryptedColumnEncryptionKey, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, new byte[] { } }; - yield return new Object[] { TCE_NullKeyEncryptionAlgorithm, typeof(ArgumentNullException), MASTER_KEY_PATH, null, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_InvalidKeyEncryptionAlgorithm, typeof(ArgumentException), MASTER_KEY_PATH, "", GenerateTestEncryptedBytes(1, 0, 256, 256) }; + yield return new Object[] { TCE_NullEncryptedColumnEncryptionKey, typeof(ArgumentNullException), PRIMARY_CERTIFICATE_PATH, ENCRYPTION_ALGORITHM, null }; + yield return new Object[] { TCE_EmptyEncryptedColumnEncryptionKey, typeof(ArgumentException), PRIMARY_CERTIFICATE_PATH, ENCRYPTION_ALGORITHM, new byte[] { } }; + yield return new Object[] { TCE_NullKeyEncryptionAlgorithm, typeof(ArgumentNullException), PRIMARY_CERTIFICATE_PATH, null, GenerateTestEncryptedBytes(1, 0, 256, 256) }; + yield return new Object[] { TCE_InvalidKeyEncryptionAlgorithm, typeof(ArgumentException), PRIMARY_CERTIFICATE_PATH, "", GenerateTestEncryptedBytes(1, 0, 256, 256) }; yield return new Object[] { TCE_LargeCertificatePathLength, typeof(ArgumentException), GenerateString(Int16.MaxValue + 1), ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; yield return new Object[] { TCE_InvalidCertificatePath, typeof(ArgumentException), "CurrentUser/My/Thumbprint/extra", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; yield return new Object[] { TCE_InvalidCertificateLocation, typeof(ArgumentException), "Invalid/My/Thumbprint", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; yield return new Object[] { TCE_InvalidCertificateStore, typeof(ArgumentException), "CurrentUser/Invalid/Thumbprint", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; + yield return new Object[] { TCE_CertificateNotFound, typeof(ArgumentException), "CurrentUser/My/JunkThumbprint", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; + yield return new Object[] { TCE_CertificateWithNoPrivateKey, typeof(ArgumentException), "CurrentUser/My/{npk_thumbprint}", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; yield return new Object[] { TCE_InvalidCertificateSignature, typeof(ArgumentException), "CurrentUser/My/", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_InvalidAlgorithmVersion, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(2, 0, 256, 256) }; - yield return new Object[] { TCE_InvalidCiphertextLengthInEncryptedCEK, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 128, 256) }; - yield return new Object[] { TCE_InvalidSignatureInEncryptedCEK, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 128) }; - yield return new Object[] { TCE_InvalidSignature, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; + yield return new Object[] { TCE_InvalidAlgorithmVersion, typeof(ArgumentException), PRIMARY_CERTIFICATE_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(2, 0, 256, 256) }; + yield return new Object[] { TCE_InvalidCiphertextLengthInEncryptedCEK, typeof(ArgumentException), PRIMARY_CERTIFICATE_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 128, 256) }; + yield return new Object[] { TCE_InvalidSignatureInEncryptedCEK, typeof(ArgumentException), PRIMARY_CERTIFICATE_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 128) }; + yield return new Object[] { TCE_InvalidSignature, typeof(ArgumentException), PRIMARY_CERTIFICATE_PATH, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; } } @@ -571,20 +595,24 @@ public class InvalidEncryptionParameters : DataAttribute private const string TCE_InvalidCertificatePath = @"Invalid certificate path: 'CurrentUser/My/Thumbprint/extra'. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; private const string TCE_InvalidCertificateLocation = @"Invalid certificate location 'Invalid' in certificate path 'Invalid/My/Thumbprint'. Use the following format: //, where is either 'LocalMachine' or 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; private const string TCE_InvalidCertificateStore = @"Invalid certificate store 'Invalid' specified in certificate path 'CurrentUser/Invalid/Thumbprint'. Expected value: 'My'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + private const string TCE_CertificateNotFound = @"Certificate with thumbprint 'JunkThumbprint' not found in certificate store 'My' in certificate location 'CurrentUser'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; + private const string TCE_CertificateWithNoPrivateKey = @"Certificate specified in key path 'CurrentUser/My/{npk_thumbprint}' does not have a private key to encrypt a column encryption key. Verify the certificate is imported correctly.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; private const string TCE_InvalidCertificateSignature = @"Empty certificate thumbprint specified in certificate path 'CurrentUser/My/'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; public override IEnumerable GetData(MethodInfo testMethod) { yield return new Object[] { TCE_NullCertificatePath, typeof(ArgumentNullException), null, ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; yield return new Object[] { TCE_EmptyCertificatePath, typeof(ArgumentException), "", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_NullEncryptedColumnEncryptionKey, typeof(ArgumentNullException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, null }; - yield return new Object[] { TCE_EmptyEncryptedColumnEncryptionKey, typeof(ArgumentException), MASTER_KEY_PATH, ENCRYPTION_ALGORITHM, new byte[] { } }; - yield return new Object[] { TCE_NullKeyEncryptionAlgorithm, typeof(ArgumentNullException), MASTER_KEY_PATH, null, GenerateTestEncryptedBytes(1, 0, 256, 256) }; - yield return new Object[] { TCE_InvalidKeyEncryptionAlgorithm, typeof(ArgumentException), MASTER_KEY_PATH, "", GenerateTestEncryptedBytes(1, 0, 256, 256) }; + yield return new Object[] { TCE_NullEncryptedColumnEncryptionKey, typeof(ArgumentNullException), PRIMARY_CERTIFICATE_PATH, ENCRYPTION_ALGORITHM, null }; + yield return new Object[] { TCE_EmptyEncryptedColumnEncryptionKey, typeof(ArgumentException), PRIMARY_CERTIFICATE_PATH, ENCRYPTION_ALGORITHM, new byte[] { } }; + yield return new Object[] { TCE_NullKeyEncryptionAlgorithm, typeof(ArgumentNullException), PRIMARY_CERTIFICATE_PATH, null, GenerateTestEncryptedBytes(1, 0, 256, 256) }; + yield return new Object[] { TCE_InvalidKeyEncryptionAlgorithm, typeof(ArgumentException), PRIMARY_CERTIFICATE_PATH, "", GenerateTestEncryptedBytes(1, 0, 256, 256) }; yield return new Object[] { TCE_LargeCertificatePathLength, typeof(ArgumentException), GenerateString(Int16.MaxValue + 1), ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; yield return new Object[] { TCE_InvalidCertificatePath, typeof(ArgumentException), "CurrentUser/My/Thumbprint/extra", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; yield return new Object[] { TCE_InvalidCertificateLocation, typeof(ArgumentException), "Invalid/My/Thumbprint", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; yield return new Object[] { TCE_InvalidCertificateStore, typeof(ArgumentException), "CurrentUser/Invalid/Thumbprint", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; + yield return new Object[] { TCE_CertificateNotFound, typeof(ArgumentException), "CurrentUser/My/JunkThumbprint", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; + yield return new Object[] { TCE_CertificateWithNoPrivateKey, typeof(ArgumentException), "CurrentUser/My/{npk_thumbprint}", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; yield return new Object[] { TCE_InvalidCertificateSignature, typeof(ArgumentException), "CurrentUser/My/", ENCRYPTION_ALGORITHM, GenerateTestEncryptedBytes(1, 0, 256, 256) }; } } @@ -630,85 +658,4 @@ public void ThrowPlatformNotSupportedExceptionInUnix() Assert.Throws(() => provider.VerifyColumnMasterKeyMetadata("", false, new byte[] { })); } } - - public class CertificateFixture : IDisposable - { - public static bool IsAdmin - { - get - { -#if NET - System.Diagnostics.Debug.Assert(OperatingSystem.IsWindows()); -#endif - return new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator); - } - } - public string certificateThumbPrint1 = "C74D53B816A971E3FF9714FE1DD2E57E1710D946"; - public static byte[] certificateRawBytes1 = new byte[] { 48, 130, 10, 36, 2, 1, 3, 48, 130, 9, 224, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 160, 130, 9, 209, 4, 130, 9, 205, 48, 130, 9, 201, 48, 130, 5, 250, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 160, 130, 5, 235, 4, 130, 5, 231, 48, 130, 5, 227, 48, 130, 5, 223, 6, 11, 42, 134, 72, 134, 247, 13, 1, 12, 10, 1, 2, 160, 130, 4, 254, 48, 130, 4, 250, 48, 28, 6, 10, 42, 134, 72, 134, 247, 13, 1, 12, 1, 3, 48, 14, 4, 8, 146, 126, 191, 6, 130, 18, 111, 71, 2, 2, 7, 208, 4, 130, 4, 216, 55, 138, 10, 135, 82, 84, 240, 82, 107, 75, 21, 156, 54, 53, 188, 62, 36, 248, 59, 17, 18, 41, 206, 171, 226, 168, 175, 59, 48, 50, 36, 26, 58, 39, 118, 231, 200, 107, 86, 144, 200, 20, 135, 22, 105, 159, 229, 116, 123, 122, 194, 69, 172, 171, 128, 251, 129, 222, 113, 27, 253, 48, 164, 116, 72, 194, 123, 12, 247, 186, 162, 40, 39, 114, 22, 118, 91, 192, 73, 122, 235, 247, 40, 89, 3, 222, 64, 214, 184, 67, 204, 188, 197, 188, 107, 126, 225, 194, 161, 110, 156, 45, 70, 26, 86, 69, 63, 120, 153, 164, 136, 15, 220, 153, 104, 50, 121, 87, 10, 180, 149, 98, 220, 73, 175, 50, 146, 231, 112, 230, 204, 132, 76, 43, 142, 7, 104, 142, 146, 92, 21, 52, 38, 59, 154, 108, 159, 192, 93, 174, 39, 134, 96, 189, 150, 77, 90, 160, 43, 127, 173, 199, 189, 4, 69, 44, 104, 148, 225, 44, 149, 167, 149, 121, 220, 232, 98, 131, 212, 130, 35, 79, 10, 173, 177, 150, 161, 91, 26, 12, 221, 136, 230, 124, 73, 96, 126, 12, 241, 99, 60, 140, 126, 140, 0, 166, 47, 16, 87, 102, 138, 45, 97, 21, 31, 224, 126, 231, 102, 99, 35, 207, 75, 22, 249, 115, 51, 106, 79, 208, 21, 108, 124, 143, 108, 130, 6, 61, 215, 227, 7, 224, 174, 193, 97, 211, 241, 224, 90, 37, 101, 147, 149, 173, 239, 113, 214, 1, 41, 69, 158, 203, 3, 63, 101, 196, 134, 7, 127, 58, 113, 243, 228, 162, 99, 75, 207, 153, 19, 193, 187, 52, 124, 85, 234, 7, 249, 75, 65, 230, 107, 247, 145, 64, 94, 106, 50, 117, 83, 138, 49, 10, 22, 211, 115, 183, 20, 119, 18, 117, 166, 153, 30, 210, 248, 118, 200, 21, 180, 118, 208, 53, 90, 243, 74, 76, 109, 106, 46, 103, 112, 197, 89, 92, 178, 83, 48, 97, 162, 73, 78, 105, 145, 213, 230, 17, 211, 121, 200, 101, 179, 158, 85, 99, 211, 68, 122, 234, 176, 4, 33, 225, 120, 139, 163, 110, 35, 199, 23, 45, 126, 199, 80, 145, 14, 74, 217, 200, 172, 216, 159, 237, 241, 157, 85, 210, 141, 180, 150, 187, 82, 48, 245, 154, 125, 60, 223, 244, 21, 20, 39, 88, 8, 153, 185, 227, 76, 78, 137, 99, 98, 81, 141, 27, 197, 41, 39, 251, 80, 27, 85, 78, 65, 15, 216, 106, 106, 113, 33, 253, 210, 46, 214, 47, 49, 89, 170, 215, 207, 62, 182, 88, 25, 186, 166, 214, 172, 63, 94, 17, 123, 235, 226, 72, 73, 204, 18, 173, 134, 92, 66, 2, 213, 151, 251, 95, 175, 38, 56, 156, 138, 96, 123, 190, 107, 59, 230, 24, 210, 224, 206, 169, 159, 95, 180, 237, 34, 194, 62, 4, 213, 228, 85, 216, 138, 157, 50, 20, 101, 160, 195, 138, 207, 18, 17, 232, 6, 73, 82, 247, 173, 50, 180, 53, 58, 156, 97, 230, 112, 211, 251, 204, 120, 188, 34, 41, 67, 83, 197, 131, 251, 176, 20, 70, 169, 116, 237, 43, 117, 45, 31, 66, 74, 152, 216, 3, 108, 102, 99, 5, 127, 76, 129, 57, 180, 90, 218, 157, 108, 85, 4, 240, 101, 149, 154, 221, 208, 70, 152, 34, 128, 57, 135, 38, 17, 139, 142, 167, 109, 73, 129, 181, 105, 45, 151, 106, 171, 166, 0, 113, 147, 141, 19, 228, 196, 88, 175, 219, 18, 213, 54, 105, 179, 8, 249, 250, 164, 86, 28, 185, 19, 60, 50, 140, 73, 237, 148, 201, 33, 204, 189, 43, 83, 163, 138, 1, 10, 13, 240, 196, 211, 221, 169, 207, 100, 167, 203, 146, 115, 70, 118, 230, 4, 224, 192, 209, 242, 144, 150, 72, 170, 149, 255, 196, 7, 91, 55, 251, 57, 127, 103, 98, 113, 83, 224, 97, 118, 132, 81, 119, 8, 105, 250, 155, 107, 149, 28, 127, 66, 127, 224, 79, 96, 9, 168, 73, 84, 228, 123, 161, 222, 179, 115, 73, 184, 62, 24, 228, 44, 156, 42, 124, 209, 29, 81, 19, 169, 24, 212, 6, 238, 239, 221, 68, 220, 106, 0, 45, 201, 129, 3, 50, 150, 244, 32, 220, 237, 20, 39, 175, 249, 80, 189, 166, 68, 251, 102, 60, 137, 93, 209, 86, 194, 55, 164, 100, 76, 220, 249, 30, 233, 101, 177, 150, 71, 28, 227, 180, 44, 115, 83, 201, 129, 44, 128, 247, 68, 175, 97, 36, 170, 76, 236, 57, 119, 240, 0, 129, 185, 35, 160, 231, 183, 56, 162, 197, 237, 186, 109, 118, 232, 84, 108, 125, 93, 92, 101, 193, 180, 210, 192, 244, 47, 55, 56, 217, 178, 200, 168, 232, 80, 223, 209, 255, 234, 146, 46, 215, 170, 197, 94, 84, 213, 233, 140, 247, 69, 185, 103, 183, 91, 23, 232, 32, 246, 244, 30, 41, 156, 28, 72, 109, 90, 127, 135, 132, 19, 136, 233, 168, 29, 98, 17, 111, 5, 185, 234, 86, 234, 114, 47, 227, 81, 77, 108, 179, 184, 91, 31, 74, 23, 29, 248, 41, 207, 8, 23, 181, 33, 99, 217, 48, 145, 97, 126, 139, 133, 11, 100, 69, 151, 146, 38, 79, 231, 155, 92, 134, 139, 189, 237, 132, 196, 95, 45, 141, 15, 26, 37, 58, 219, 10, 0, 36, 221, 240, 82, 117, 163, 121, 141, 206, 21, 180, 195, 58, 109, 56, 123, 152, 206, 116, 161, 221, 125, 248, 23, 31, 240, 227, 186, 52, 171, 147, 51, 39, 203, 92, 205, 182, 146, 149, 111, 27, 59, 219, 234, 216, 52, 89, 22, 224, 76, 62, 94, 76, 131, 48, 162, 134, 161, 177, 44, 205, 101, 253, 13, 237, 40, 29, 72, 224, 121, 74, 189, 57, 81, 58, 169, 178, 173, 157, 182, 143, 205, 64, 225, 137, 188, 235, 43, 195, 3, 187, 105, 113, 72, 82, 153, 58, 97, 38, 251, 212, 149, 191, 11, 153, 157, 106, 16, 236, 237, 209, 210, 208, 19, 68, 92, 176, 65, 24, 115, 181, 94, 24, 126, 2, 216, 63, 200, 136, 178, 92, 248, 11, 128, 68, 122, 14, 46, 234, 48, 142, 219, 92, 29, 136, 70, 200, 52, 78, 70, 160, 215, 113, 102, 190, 66, 16, 69, 120, 25, 201, 23, 209, 41, 79, 25, 151, 38, 38, 82, 244, 143, 121, 216, 111, 91, 167, 232, 32, 234, 243, 195, 168, 240, 135, 188, 1, 92, 145, 77, 240, 107, 20, 82, 147, 168, 132, 78, 115, 206, 95, 47, 8, 80, 91, 255, 28, 38, 161, 52, 168, 211, 236, 143, 238, 146, 172, 104, 2, 254, 240, 229, 210, 225, 47, 41, 76, 134, 5, 20, 203, 188, 48, 195, 120, 103, 234, 94, 217, 142, 238, 254, 131, 146, 214, 106, 212, 229, 201, 79, 151, 198, 100, 132, 99, 228, 82, 182, 94, 216, 226, 163, 42, 113, 110, 201, 70, 221, 127, 242, 7, 176, 60, 121, 158, 37, 56, 6, 156, 191, 75, 94, 222, 10, 155, 39, 64, 172, 216, 106, 210, 202, 246, 66, 83, 107, 250, 17, 134, 222, 212, 71, 200, 215, 103, 35, 82, 225, 106, 17, 106, 74, 18, 130, 236, 175, 45, 145, 155, 169, 88, 72, 244, 3, 38, 245, 208, 49, 129, 205, 48, 19, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 21, 49, 6, 4, 4, 1, 0, 0, 0, 48, 87, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 20, 49, 74, 30, 72, 0, 100, 0, 99, 0, 99, 0, 52, 0, 51, 0, 48, 0, 56, 0, 56, 0, 45, 0, 50, 0, 57, 0, 54, 0, 53, 0, 45, 0, 52, 0, 57, 0, 97, 0, 48, 0, 45, 0, 56, 0, 51, 0, 54, 0, 53, 0, 45, 0, 50, 0, 52, 0, 101, 0, 52, 0, 97, 0, 52, 0, 49, 0, 100, 0, 55, 0, 50, 0, 52, 0, 48, 48, 93, 6, 9, 43, 6, 1, 4, 1, 130, 55, 17, 1, 49, 80, 30, 78, 0, 77, 0, 105, 0, 99, 0, 114, 0, 111, 0, 115, 0, 111, 0, 102, 0, 116, 0, 32, 0, 83, 0, 116, 0, 114, 0, 111, 0, 110, 0, 103, 0, 32, 0, 67, 0, 114, 0, 121, 0, 112, 0, 116, 0, 111, 0, 103, 0, 114, 0, 97, 0, 112, 0, 104, 0, 105, 0, 99, 0, 32, 0, 80, 0, 114, 0, 111, 0, 118, 0, 105, 0, 100, 0, 101, 0, 114, 48, 130, 3, 199, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 6, 160, 130, 3, 184, 48, 130, 3, 180, 2, 1, 0, 48, 130, 3, 173, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 48, 28, 6, 10, 42, 134, 72, 134, 247, 13, 1, 12, 1, 3, 48, 14, 4, 8, 206, 244, 28, 93, 203, 68, 165, 233, 2, 2, 7, 208, 128, 130, 3, 128, 74, 136, 80, 43, 195, 182, 181, 122, 132, 229, 10, 181, 229, 1, 78, 122, 145, 95, 16, 236, 242, 107, 9, 141, 186, 205, 32, 139, 154, 132, 184, 180, 80, 26, 3, 85, 196, 10, 33, 216, 101, 105, 172, 196, 77, 222, 232, 229, 37, 199, 6, 189, 152, 8, 203, 15, 231, 164, 140, 163, 120, 23, 137, 34, 16, 241, 186, 64, 11, 241, 210, 160, 186, 90, 55, 39, 21, 210, 145, 74, 151, 40, 122, 221, 240, 191, 185, 115, 85, 208, 125, 136, 51, 210, 137, 124, 155, 65, 135, 50, 35, 233, 223, 157, 131, 108, 11, 142, 152, 217, 162, 163, 218, 47, 89, 255, 229, 21, 224, 139, 187, 4, 175, 251, 248, 8, 18, 16, 112, 134, 75, 17, 90, 246, 62, 150, 31, 207, 95, 172, 5, 220, 135, 201, 179, 247, 193, 177, 23, 5, 170, 207, 66, 219, 145, 117, 99, 167, 238, 100, 158, 169, 44, 22, 199, 132, 38, 67, 203, 66, 187, 53, 216, 98, 113, 76, 142, 153, 36, 238, 110, 152, 251, 68, 6, 154, 255, 51, 65, 75, 91, 9, 121, 86, 116, 35, 224, 47, 220, 194, 17, 136, 175, 76, 165, 210, 153, 89, 104, 197, 133, 200, 49, 173, 1, 167, 5, 88, 183, 58, 193, 146, 30, 60, 129, 195, 3, 16, 78, 87, 167, 135, 182, 182, 150, 68, 116, 161, 116, 125, 180, 155, 103, 63, 0, 98, 27, 179, 142, 64, 73, 31, 35, 63, 138, 137, 30, 169, 149, 221, 104, 21, 182, 23, 67, 246, 2, 162, 217, 165, 238, 124, 229, 149, 84, 5, 203, 174, 149, 79, 153, 25, 153, 233, 213, 86, 250, 10, 42, 6, 226, 113, 123, 90, 76, 153, 39, 203, 237, 124, 36, 191, 232, 132, 127, 82, 163, 109, 100, 121, 54, 254, 116, 155, 26, 255, 50, 150, 140, 172, 240, 208, 245, 65, 72, 49, 183, 149, 220, 244, 120, 193, 37, 222, 144, 137, 82, 168, 233, 13, 179, 2, 217, 29, 177, 4, 136, 69, 192, 133, 249, 180, 9, 62, 162, 216, 251, 164, 188, 173, 143, 149, 32, 204, 255, 246, 249, 33, 216, 75, 23, 127, 215, 134, 69, 79, 112, 213, 198, 89, 44, 51, 19, 226, 16, 210, 125, 212, 232, 18, 252, 178, 93, 245, 33, 62, 81, 207, 78, 167, 144, 238, 251, 27, 194, 21, 53, 44, 63, 58, 26, 176, 75, 79, 164, 67, 59, 80, 17, 54, 209, 58, 184, 2, 36, 202, 135, 91, 35, 78, 55, 203, 134, 238, 79, 178, 84, 242, 46, 223, 131, 227, 87, 255, 182, 244, 117, 162, 60, 134, 161, 49, 59, 95, 64, 190, 30, 195, 100, 106, 7, 120, 181, 202, 122, 174, 234, 30, 11, 88, 65, 238, 53, 64, 243, 233, 185, 168, 34, 8, 58, 233, 171, 210, 104, 105, 93, 49, 206, 11, 40, 172, 248, 204, 80, 128, 53, 143, 54, 95, 92, 70, 152, 209, 193, 116, 252, 138, 19, 50, 249, 43, 14, 225, 167, 8, 205, 112, 103, 79, 223, 14, 141, 147, 70, 197, 91, 11, 117, 202, 19, 180, 240, 21, 118, 108, 25, 63, 54, 94, 156, 112, 109, 16, 216, 113, 192, 246, 207, 156, 203, 65, 75, 143, 157, 125, 158, 151, 167, 207, 96, 6, 162, 97, 66, 114, 95, 227, 52, 44, 98, 121, 139, 181, 240, 89, 27, 59, 156, 189, 93, 28, 48, 165, 11, 245, 102, 198, 29, 5, 6, 180, 147, 58, 130, 65, 201, 10, 164, 193, 93, 168, 96, 156, 89, 225, 139, 70, 245, 74, 128, 3, 141, 133, 137, 21, 163, 77, 3, 19, 226, 35, 248, 156, 56, 56, 37, 221, 69, 67, 214, 3, 152, 149, 224, 92, 72, 173, 39, 196, 229, 153, 67, 151, 190, 115, 20, 70, 126, 210, 140, 109, 186, 46, 82, 88, 185, 96, 1, 254, 161, 217, 130, 226, 133, 18, 103, 175, 132, 249, 102, 51, 229, 192, 94, 44, 10, 25, 197, 237, 77, 196, 1, 253, 153, 78, 237, 151, 136, 89, 203, 113, 244, 217, 235, 252, 31, 116, 139, 233, 40, 197, 22, 176, 157, 130, 109, 149, 215, 11, 20, 3, 156, 239, 29, 250, 95, 188, 241, 184, 117, 108, 216, 74, 91, 169, 186, 122, 175, 214, 36, 62, 240, 142, 107, 172, 7, 250, 31, 101, 75, 83, 255, 56, 8, 231, 200, 194, 154, 105, 202, 170, 207, 252, 128, 10, 249, 53, 41, 168, 94, 225, 163, 10, 251, 149, 64, 10, 144, 252, 44, 136, 149, 119, 183, 7, 230, 87, 160, 46, 62, 185, 82, 218, 213, 125, 62, 70, 43, 27, 5, 181, 50, 193, 11, 30, 0, 8, 81, 94, 169, 171, 143, 113, 235, 171, 38, 129, 116, 11, 191, 75, 235, 185, 184, 178, 36, 193, 174, 177, 51, 87, 163, 142, 52, 62, 161, 237, 139, 50, 51, 227, 188, 164, 106, 233, 209, 8, 237, 241, 92, 145, 51, 6, 36, 197, 24, 255, 143, 5, 144, 43, 87, 242, 208, 251, 79, 171, 90, 103, 219, 73, 242, 95, 36, 48, 95, 127, 40, 128, 201, 80, 79, 74, 226, 25, 43, 50, 56, 180, 59, 84, 148, 110, 151, 9, 45, 4, 212, 172, 31, 189, 44, 115, 59, 169, 48, 59, 48, 31, 48, 7, 6, 5, 43, 14, 3, 2, 26, 4, 20, 238, 91, 24, 104, 64, 45, 237, 63, 114, 36, 111, 106, 82, 43, 251, 110, 60, 159, 42, 178, 4, 20, 20, 49, 70, 55, 115, 247, 221, 156, 47, 189, 197, 19, 116, 77, 161, 163, 216, 77, 166, 144, 2, 2, 7, 208 }; - public string certificateThumbPrint2 = "4281446463C6F7F5B8EDFFA4BD6E345E46857CAD"; - public static byte[] certificateRawBytes2 = new byte[] { 48, 130, 10, 36, 2, 1, 3, 48, 130, 9, 224, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 160, 130, 9, 209, 4, 130, 9, 205, 48, 130, 9, 201, 48, 130, 5, 250, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 160, 130, 5, 235, 4, 130, 5, 231, 48, 130, 5, 227, 48, 130, 5, 223, 6, 11, 42, 134, 72, 134, 247, 13, 1, 12, 10, 1, 2, 160, 130, 4, 254, 48, 130, 4, 250, 48, 28, 6, 10, 42, 134, 72, 134, 247, 13, 1, 12, 1, 3, 48, 14, 4, 8, 95, 228, 98, 55, 21, 153, 43, 16, 2, 2, 7, 208, 4, 130, 4, 216, 210, 4, 83, 193, 62, 47, 171, 147, 165, 139, 74, 78, 118, 172, 54, 56, 118, 81, 203, 190, 58, 5, 220, 181, 64, 1, 213, 5, 156, 164, 86, 59, 63, 230, 250, 57, 26, 236, 10, 195, 200, 80, 217, 38, 214, 116, 250, 224, 236, 54, 59, 208, 206, 128, 167, 122, 89, 0, 195, 145, 41, 63, 122, 160, 157, 21, 226, 205, 179, 166, 243, 92, 94, 71, 61, 208, 183, 153, 105, 24, 232, 255, 139, 188, 32, 109, 120, 41, 18, 218, 212, 71, 219, 139, 182, 59, 78, 46, 97, 176, 67, 125, 239, 234, 107, 47, 202, 71, 81, 100, 243, 136, 162, 39, 197, 207, 4, 224, 52, 62, 104, 88, 44, 42, 234, 18, 70, 55, 248, 251, 121, 215, 162, 77, 109, 189, 135, 86, 219, 69, 208, 92, 147, 163, 21, 50, 0, 87, 166, 8, 39, 21, 72, 107, 75, 214, 165, 238, 50, 145, 2, 65, 138, 179, 21, 87, 149, 218, 163, 51, 214, 17, 93, 252, 224, 6, 245, 242, 132, 63, 100, 223, 136, 166, 89, 253, 171, 204, 215, 191, 170, 25, 40, 44, 14, 32, 25, 22, 149, 161, 62, 145, 61, 162, 32, 116, 7, 201, 33, 159, 128, 248, 172, 42, 33, 51, 248, 187, 135, 58, 107, 23, 7, 39, 21, 114, 186, 222, 132, 117, 193, 132, 94, 57, 213, 80, 9, 86, 186, 62, 201, 40, 12, 196, 207, 23, 182, 127, 245, 139, 18, 62, 107, 82, 58, 156, 156, 17, 29, 173, 60, 227, 180, 73, 245, 165, 16, 186, 246, 64, 178, 40, 66, 2, 119, 61, 78, 246, 214, 226, 89, 225, 85, 183, 163, 108, 82, 36, 109, 216, 195, 141, 174, 123, 162, 91, 128, 169, 37, 68, 121, 170, 80, 236, 27, 109, 173, 223, 6, 37, 37, 249, 166, 219, 226, 116, 236, 184, 212, 47, 70, 249, 89, 195, 216, 157, 227, 137, 247, 179, 148, 16, 40, 217, 220, 247, 97, 42, 120, 63, 3, 14, 104, 79, 111, 160, 245, 210, 33, 50, 6, 36, 90, 225, 206, 104, 70, 4, 191, 79, 16, 237, 200, 125, 92, 215, 175, 196, 143, 134, 95, 112, 251, 58, 68, 165, 157, 80, 82, 78, 203, 34, 131, 163, 136, 200, 4, 117, 174, 197, 159, 175, 14, 5, 110, 99, 70, 235, 91, 214, 136, 218, 14, 203, 28, 153, 48, 34, 60, 10, 202, 129, 212, 146, 83, 104, 236, 228, 248, 125, 120, 98, 174, 173, 216, 146, 254, 128, 0, 206, 64, 119, 56, 71, 153, 81, 155, 40, 195, 114, 41, 73, 108, 77, 229, 98, 15, 216, 164, 13, 139, 81, 9, 143, 137, 164, 122, 35, 192, 93, 87, 85, 88, 230, 168, 148, 233, 5, 76, 244, 116, 121, 157, 27, 174, 231, 58, 84, 159, 102, 149, 163, 142, 128, 195, 10, 214, 11, 164, 197, 182, 189, 33, 177, 232, 39, 44, 225, 74, 237, 197, 52, 82, 76, 105, 9, 221, 110, 251, 62, 255, 194, 24, 93, 184, 19, 220, 119, 127, 76, 198, 181, 14, 136, 106, 49, 241, 164, 18, 62, 80, 18, 88, 114, 167, 138, 183, 72, 160, 64, 27, 140, 160, 74, 113, 40, 212, 223, 128, 23, 113, 192, 162, 184, 234, 141, 207, 3, 246, 40, 2, 89, 184, 191, 67, 121, 16, 187, 117, 141, 163, 187, 170, 124, 169, 67, 148, 226, 202, 132, 61, 95, 7, 242, 116, 252, 228, 17, 102, 201, 178, 77, 215, 164, 204, 210, 31, 100, 243, 242, 190, 151, 96, 173, 74, 195, 214, 233, 78, 187, 73, 124, 183, 38, 124, 33, 108, 226, 113, 120, 25, 87, 201, 49, 134, 106, 127, 206, 234, 40, 37, 199, 56, 112, 0, 172, 136, 68, 8, 145, 225, 78, 186, 170, 121, 218, 37, 186, 80, 207, 29, 180, 129, 159, 178, 162, 152, 107, 39, 229, 192, 237, 226, 172, 88, 117, 144, 229, 124, 67, 74, 156, 81, 211, 118, 93, 188, 93, 209, 170, 240, 136, 37, 18, 181, 20, 48, 70, 79, 37, 169, 184, 240, 101, 153, 230, 10, 212, 36, 29, 201, 27, 39, 107, 221, 179, 226, 19, 199, 108, 158, 78, 217, 49, 255, 131, 36, 194, 37, 133, 47, 36, 207, 13, 16, 115, 179, 220, 57, 248, 194, 101, 181, 222, 170, 240, 120, 37, 50, 87, 198, 14, 251, 138, 115, 33, 231, 29, 240, 172, 130, 199, 77, 53, 245, 43, 178, 61, 103, 28, 33, 175, 247, 67, 232, 3, 139, 198, 115, 93, 146, 71, 154, 206, 118, 163, 99, 213, 241, 174, 20, 247, 181, 12, 112, 165, 116, 179, 220, 52, 200, 206, 162, 105, 12, 30, 212, 199, 179, 243, 176, 156, 113, 51, 142, 138, 70, 179, 130, 28, 118, 98, 7, 46, 26, 100, 200, 215, 16, 80, 138, 113, 160, 107, 209, 18, 85, 2, 69, 235, 2, 217, 80, 238, 212, 108, 18, 68, 63, 24, 174, 60, 253, 127, 94, 255, 249, 181, 98, 243, 240, 172, 109, 242, 155, 42, 70, 155, 38, 214, 231, 206, 60, 205, 46, 68, 77, 128, 192, 141, 4, 73, 54, 137, 32, 71, 20, 68, 11, 204, 124, 97, 205, 246, 80, 209, 175, 165, 121, 60, 195, 104, 104, 60, 2, 99, 142, 82, 121, 136, 118, 226, 178, 158, 80, 0, 159, 131, 208, 232, 46, 150, 196, 154, 196, 50, 183, 85, 170, 242, 218, 232, 236, 225, 52, 46, 109, 237, 127, 68, 251, 25, 105, 239, 32, 59, 205, 174, 131, 95, 75, 93, 218, 168, 173, 6, 152, 111, 251, 201, 146, 19, 230, 2, 32, 73, 32, 64, 101, 124, 96, 155, 101, 211, 232, 249, 143, 177, 147, 17, 187, 246, 46, 202, 155, 113, 236, 181, 70, 118, 220, 87, 20, 27, 17, 255, 223, 65, 217, 126, 5, 246, 161, 95, 186, 194, 77, 46, 26, 128, 253, 108, 178, 246, 121, 133, 172, 172, 75, 59, 12, 118, 7, 146, 154, 51, 94, 243, 112, 232, 103, 239, 159, 64, 183, 187, 79, 22, 43, 7, 250, 205, 183, 201, 178, 235, 80, 154, 233, 232, 125, 78, 62, 203, 132, 33, 4, 185, 234, 59, 190, 116, 133, 240, 41, 172, 207, 36, 177, 206, 49, 18, 40, 23, 177, 250, 36, 243, 59, 160, 24, 245, 218, 53, 234, 79, 17, 68, 19, 165, 156, 16, 250, 18, 111, 60, 179, 142, 168, 4, 67, 212, 11, 230, 113, 46, 105, 234, 98, 60, 36, 218, 202, 93, 54, 53, 160, 247, 144, 244, 225, 219, 101, 143, 239, 183, 75, 6, 144, 68, 10, 96, 98, 191, 251, 230, 243, 154, 236, 113, 164, 139, 155, 250, 133, 104, 45, 21, 106, 221, 20, 139, 191, 33, 38, 103, 135, 95, 5, 237, 173, 185, 173, 2, 40, 196, 114, 3, 174, 13, 201, 219, 150, 100, 30, 212, 38, 60, 106, 112, 244, 229, 34, 155, 169, 51, 62, 96, 154, 90, 225, 221, 103, 8, 201, 235, 189, 170, 42, 44, 255, 210, 69, 155, 119, 153, 8, 164, 171, 97, 173, 166, 68, 207, 170, 50, 92, 17, 2, 32, 182, 46, 136, 189, 187, 148, 206, 205, 127, 179, 32, 31, 77, 12, 141, 19, 126, 5, 82, 221, 47, 87, 206, 198, 146, 226, 128, 144, 64, 124, 49, 129, 205, 48, 19, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 21, 49, 6, 4, 4, 1, 0, 0, 0, 48, 87, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 20, 49, 74, 30, 72, 0, 98, 0, 53, 0, 55, 0, 56, 0, 48, 0, 53, 0, 52, 0, 52, 0, 45, 0, 52, 0, 56, 0, 99, 0, 48, 0, 45, 0, 52, 0, 55, 0, 56, 0, 52, 0, 45, 0, 57, 0, 50, 0, 98, 0, 52, 0, 45, 0, 56, 0, 99, 0, 49, 0, 101, 0, 102, 0, 56, 0, 54, 0, 101, 0, 54, 0, 100, 0, 54, 0, 54, 48, 93, 6, 9, 43, 6, 1, 4, 1, 130, 55, 17, 1, 49, 80, 30, 78, 0, 77, 0, 105, 0, 99, 0, 114, 0, 111, 0, 115, 0, 111, 0, 102, 0, 116, 0, 32, 0, 83, 0, 116, 0, 114, 0, 111, 0, 110, 0, 103, 0, 32, 0, 67, 0, 114, 0, 121, 0, 112, 0, 116, 0, 111, 0, 103, 0, 114, 0, 97, 0, 112, 0, 104, 0, 105, 0, 99, 0, 32, 0, 80, 0, 114, 0, 111, 0, 118, 0, 105, 0, 100, 0, 101, 0, 114, 48, 130, 3, 199, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 6, 160, 130, 3, 184, 48, 130, 3, 180, 2, 1, 0, 48, 130, 3, 173, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 48, 28, 6, 10, 42, 134, 72, 134, 247, 13, 1, 12, 1, 3, 48, 14, 4, 8, 109, 84, 234, 217, 158, 100, 175, 217, 2, 2, 7, 208, 128, 130, 3, 128, 90, 89, 57, 156, 113, 214, 108, 227, 208, 52, 188, 14, 47, 164, 163, 155, 102, 18, 248, 55, 200, 122, 232, 224, 246, 212, 57, 2, 121, 60, 244, 110, 223, 203, 184, 67, 115, 179, 191, 220, 45, 209, 143, 220, 17, 53, 61, 179, 105, 25, 150, 102, 108, 168, 140, 67, 141, 86, 165, 159, 139, 29, 23, 173, 40, 81, 79, 245, 12, 154, 11, 154, 208, 199, 34, 25, 46, 112, 30, 175, 71, 124, 20, 64, 128, 150, 156, 241, 198, 55, 103, 242, 169, 160, 232, 138, 160, 189, 30, 66, 73, 134, 28, 1, 32, 19, 151, 249, 75, 179, 252, 0, 244, 116, 209, 35, 116, 199, 171, 120, 18, 234, 17, 47, 70, 115, 154, 76, 170, 36, 166, 140, 190, 168, 99, 169, 130, 200, 220, 55, 38, 56, 145, 4, 119, 149, 184, 242, 214, 46, 181, 101, 25, 97, 102, 39, 240, 123, 83, 87, 69, 107, 159, 69, 136, 101, 88, 45, 2, 254, 111, 255, 202, 184, 213, 237, 98, 120, 235, 247, 134, 85, 155, 103, 232, 99, 218, 169, 219, 55, 232, 126, 133, 172, 179, 210, 82, 40, 42, 101, 157, 213, 42, 217, 88, 111, 101, 88, 202, 30, 212, 117, 225, 169, 164, 105, 225, 128, 251, 66, 148, 38, 7, 166, 125, 120, 135, 158, 139, 222, 245, 170, 5, 8, 102, 51, 193, 173, 136, 141, 183, 233, 168, 185, 208, 198, 238, 176, 63, 6, 167, 53, 27, 15, 226, 47, 37, 154, 1, 126, 252, 77, 83, 5, 21, 221, 166, 171, 9, 177, 181, 85, 83, 6, 154, 164, 24, 206, 48, 88, 243, 82, 103, 80, 9, 1, 169, 225, 255, 75, 10, 150, 17, 242, 49, 110, 194, 114, 9, 69, 15, 183, 20, 33, 43, 113, 79, 131, 115, 102, 230, 138, 139, 191, 29, 249, 175, 222, 231, 87, 98, 213, 108, 107, 30, 10, 126, 114, 3, 81, 67, 192, 26, 125, 31, 87, 83, 75, 113, 254, 122, 143, 227, 104, 67, 58, 155, 93, 177, 158, 84, 50, 50, 197, 125, 17, 252, 87, 194, 176, 160, 140, 220, 47, 209, 188, 158, 89, 167, 166, 241, 212, 44, 108, 208, 52, 39, 128, 198, 157, 93, 56, 154, 184, 136, 9, 48, 93, 239, 244, 140, 204, 139, 91, 181, 160, 117, 230, 142, 233, 205, 18, 178, 212, 103, 148, 188, 201, 106, 70, 172, 212, 215, 145, 62, 166, 139, 165, 241, 178, 100, 150, 119, 234, 44, 107, 123, 73, 105, 82, 222, 254, 40, 49, 198, 167, 88, 165, 176, 187, 124, 161, 97, 133, 188, 159, 234, 90, 110, 99, 121, 191, 123, 254, 86, 108, 164, 231, 225, 227, 209, 237, 155, 26, 148, 241, 91, 253, 18, 68, 137, 175, 184, 4, 160, 176, 109, 72, 177, 84, 229, 115, 95, 98, 155, 117, 96, 54, 226, 141, 163, 109, 58, 189, 67, 15, 246, 151, 136, 169, 88, 46, 160, 102, 113, 226, 92, 122, 209, 140, 52, 105, 235, 254, 127, 131, 235, 246, 146, 67, 24, 97, 196, 193, 248, 246, 214, 55, 52, 254, 214, 193, 77, 192, 91, 45, 17, 204, 77, 193, 211, 166, 152, 214, 79, 173, 167, 11, 49, 253, 9, 167, 249, 168, 228, 198, 164, 61, 209, 118, 5, 170, 128, 128, 217, 130, 90, 64, 13, 79, 191, 142, 29, 211, 101, 225, 173, 190, 124, 132, 149, 107, 178, 129, 102, 135, 23, 109, 5, 75, 207, 185, 21, 227, 184, 41, 92, 83, 180, 164, 77, 140, 184, 216, 66, 25, 104, 165, 158, 95, 234, 78, 153, 215, 26, 253, 100, 115, 101, 149, 58, 5, 49, 204, 213, 35, 188, 200, 201, 74, 248, 94, 143, 231, 215, 59, 145, 54, 28, 197, 156, 197, 131, 80, 114, 212, 236, 133, 25, 178, 57, 28, 241, 237, 56, 220, 195, 19, 234, 13, 122, 109, 81, 205, 163, 204, 122, 82, 10, 166, 44, 44, 74, 229, 251, 200, 42, 128, 223, 29, 182, 95, 180, 3, 100, 235, 56, 34, 157, 84, 23, 142, 6, 147, 187, 145, 97, 185, 6, 254, 48, 110, 125, 41, 239, 163, 169, 196, 145, 120, 115, 213, 57, 85, 118, 121, 211, 14, 56, 54, 34, 171, 101, 37, 190, 232, 55, 64, 238, 119, 150, 67, 136, 116, 193, 175, 100, 38, 2, 163, 28, 27, 250, 88, 46, 56, 18, 20, 68, 115, 111, 172, 112, 239, 55, 213, 5, 2, 91, 134, 98, 53, 99, 90, 6, 18, 199, 74, 241, 162, 85, 182, 134, 42, 233, 38, 192, 248, 200, 165, 194, 85, 232, 129, 170, 36, 155, 44, 14, 31, 234, 191, 172, 204, 146, 151, 24, 214, 14, 174, 163, 88, 15, 198, 119, 104, 198, 20, 139, 7, 199, 232, 144, 30, 129, 243, 234, 148, 231, 227, 87, 76, 81, 160, 149, 26, 233, 108, 133, 127, 193, 82, 233, 19, 179, 62, 210, 244, 118, 249, 202, 245, 5, 71, 37, 53, 249, 30, 0, 43, 190, 110, 96, 184, 239, 176, 52, 55, 109, 110, 111, 53, 193, 192, 200, 180, 5, 88, 32, 174, 106, 17, 23, 127, 190, 166, 34, 57, 161, 146, 255, 197, 222, 2, 107, 227, 61, 215, 36, 253, 1, 122, 234, 234, 156, 79, 48, 59, 48, 31, 48, 7, 6, 5, 43, 14, 3, 2, 26, 4, 20, 207, 75, 252, 75, 27, 80, 128, 157, 13, 40, 231, 80, 100, 232, 103, 176, 94, 95, 145, 147, 4, 20, 48, 158, 42, 31, 56, 126, 217, 230, 42, 16, 115, 84, 206, 98, 189, 26, 78, 38, 171, 12, 2, 2, 7, 208 }; - public static byte[] certificateRawBytes3 = new byte[] { 48, 130, 10, 44, 2, 1, 3, 48, 130, 9, 232, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 160, 130, 9, 217, 4, 130, 9, 213, 48, 130, 9, 209, 48, 130, 5, 250, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 160, 130, 5, 235, 4, 130, 5, 231, 48, 130, 5, 227, 48, 130, 5, 223, 6, 11, 42, 134, 72, 134, 247, 13, 1, 12, 10, 1, 2, 160, 130, 4, 254, 48, 130, 4, 250, 48, 28, 6, 10, 42, 134, 72, 134, 247, 13, 1, 12, 1, 3, 48, 14, 4, 8, 201, 182, 155, 228, 101, 5, 93, 99, 2, 2, 7, 208, 4, 130, 4, 216, 10, 49, 25, 129, 251, 253, 104, 241, 120, 23, 72, 166, 23, 144, 117, 109, 170, 226, 13, 202, 202, 135, 74, 114, 79, 208, 250, 193, 195, 26, 118, 85, 45, 17, 43, 75, 29, 44, 52, 18, 255, 66, 137, 221, 33, 65, 186, 142, 90, 33, 2, 4, 177, 170, 58, 244, 60, 82, 40, 77, 64, 168, 77, 2, 29, 36, 210, 107, 213, 47, 160, 181, 82, 202, 221, 5, 202, 46, 222, 27, 228, 181, 14, 128, 2, 65, 57, 13, 128, 211, 53, 168, 207, 150, 76, 135, 1, 84, 243, 72, 151, 205, 184, 164, 221, 138, 107, 162, 163, 248, 8, 107, 102, 223, 173, 12, 192, 119, 77, 154, 224, 213, 198, 90, 183, 161, 188, 4, 221, 191, 76, 179, 87, 158, 140, 179, 198, 253, 87, 148, 162, 94, 186, 171, 36, 251, 214, 44, 164, 240, 131, 52, 204, 192, 163, 166, 108, 150, 108, 85, 138, 181, 45, 159, 7, 106, 47, 1, 189, 81, 104, 80, 66, 21, 133, 111, 226, 238, 73, 126, 191, 61, 169, 123, 210, 37, 228, 121, 231, 118, 134, 176, 15, 206, 137, 99, 253, 172, 21, 14, 249, 32, 226, 122, 101, 41, 98, 23, 35, 183, 23, 66, 102, 249, 175, 41, 103, 161, 211, 57, 208, 146, 182, 229, 55, 1, 251, 212, 253, 83, 119, 224, 153, 22, 198, 147, 9, 99, 101, 214, 142, 202, 197, 187, 197, 21, 199, 186, 231, 143, 116, 4, 253, 70, 238, 220, 98, 236, 110, 20, 153, 134, 52, 40, 245, 167, 76, 28, 24, 242, 40, 57, 97, 227, 223, 239, 249, 14, 34, 40, 53, 145, 35, 248, 37, 251, 84, 65, 255, 0, 143, 54, 73, 38, 8, 99, 160, 70, 49, 7, 238, 31, 238, 62, 180, 125, 71, 166, 96, 52, 37, 134, 229, 198, 150, 206, 102, 241, 51, 240, 186, 120, 74, 55, 114, 46, 190, 85, 15, 89, 4, 126, 89, 247, 120, 241, 119, 110, 225, 126, 99, 143, 179, 7, 117, 242, 225, 197, 247, 130, 105, 59, 129, 127, 222, 207, 20, 108, 241, 149, 124, 156, 175, 187, 105, 79, 23, 245, 131, 106, 12, 111, 228, 124, 128, 147, 204, 21, 122, 93, 52, 251, 197, 119, 204, 195, 111, 8, 43, 226, 50, 39, 66, 179, 84, 108, 71, 35, 10, 35, 129, 96, 0, 110, 5, 92, 223, 77, 49, 237, 184, 153, 163, 111, 29, 241, 52, 62, 190, 157, 22, 40, 26, 6, 231, 156, 90, 210, 16, 183, 154, 208, 244, 113, 213, 110, 121, 56, 131, 240, 136, 77, 171, 87, 215, 87, 134, 188, 14, 104, 217, 35, 218, 215, 36, 50, 5, 72, 137, 63, 242, 29, 0, 28, 127, 172, 190, 166, 239, 100, 172, 172, 23, 131, 237, 32, 73, 92, 157, 178, 82, 87, 0, 131, 53, 226, 176, 125, 221, 33, 75, 201, 164, 24, 253, 23, 130, 41, 129, 13, 223, 10, 22, 231, 99, 220, 171, 0, 205, 239, 89, 47, 224, 139, 181, 201, 54, 125, 105, 206, 168, 62, 145, 182, 114, 209, 134, 48, 0, 90, 143, 163, 161, 117, 167, 74, 6, 229, 92, 10, 163, 27, 49, 25, 139, 44, 140, 35, 107, 46, 74, 124, 180, 88, 92, 49, 27, 53, 93, 20, 105, 35, 8, 242, 28, 106, 106, 80, 220, 138, 169, 89, 154, 9, 57, 173, 83, 67, 136, 119, 200, 78, 152, 47, 100, 166, 119, 23, 208, 46, 57, 213, 6, 18, 21, 29, 185, 174, 154, 245, 154, 122, 53, 155, 28, 110, 139, 20, 236, 97, 97, 155, 65, 128, 45, 27, 95, 117, 126, 150, 87, 70, 37, 209, 65, 178, 27, 17, 59, 180, 176, 172, 104, 184, 144, 2, 238, 219, 210, 250, 168, 125, 24, 113, 36, 28, 105, 107, 34, 241, 175, 251, 71, 110, 72, 64, 15, 194, 39, 105, 18, 168, 60, 13, 50, 62, 191, 196, 255, 5, 106, 100, 158, 3, 45, 148, 238, 239, 64, 3, 21, 198, 50, 165, 107, 245, 195, 103, 138, 108, 153, 48, 105, 193, 106, 31, 82, 126, 202, 144, 215, 64, 138, 23, 160, 13, 233, 88, 199, 77, 46, 231, 124, 237, 26, 24, 206, 53, 162, 53, 224, 41, 178, 17, 44, 148, 22, 151, 192, 140, 121, 12, 68, 31, 238, 82, 24, 21, 116, 219, 172, 138, 179, 91, 10, 54, 235, 19, 49, 145, 67, 5, 55, 171, 23, 196, 237, 252, 107, 197, 18, 207, 48, 75, 229, 197, 152, 60, 33, 246, 97, 231, 28, 163, 222, 213, 8, 169, 178, 54, 217, 174, 208, 181, 173, 59, 174, 100, 65, 137, 130, 47, 42, 59, 176, 209, 225, 254, 59, 193, 40, 110, 195, 139, 166, 11, 44, 134, 251, 243, 46, 200, 174, 96, 21, 244, 10, 226, 244, 40, 115, 30, 241, 143, 100, 247, 0, 227, 248, 239, 78, 249, 80, 0, 35, 73, 107, 250, 156, 21, 36, 138, 203, 235, 84, 154, 46, 184, 12, 139, 246, 255, 89, 162, 249, 107, 237, 55, 148, 123, 197, 148, 254, 90, 201, 255, 249, 219, 170, 138, 212, 179, 175, 93, 42, 44, 195, 71, 108, 77, 187, 145, 147, 82, 154, 29, 166, 90, 35, 124, 157, 68, 102, 136, 235, 143, 100, 41, 248, 103, 139, 50, 56, 108, 233, 65, 228, 42, 152, 157, 102, 176, 56, 249, 86, 198, 236, 75, 61, 206, 240, 255, 55, 20, 222, 105, 19, 221, 7, 66, 194, 20, 186, 152, 120, 90, 68, 231, 17, 37, 248, 172, 75, 172, 129, 209, 220, 180, 123, 65, 32, 215, 146, 245, 5, 28, 255, 213, 35, 10, 59, 141, 15, 111, 5, 176, 189, 54, 209, 38, 234, 89, 52, 203, 128, 17, 147, 202, 171, 186, 8, 10, 89, 223, 83, 119, 28, 102, 11, 108, 70, 235, 43, 6, 146, 152, 132, 106, 192, 10, 80, 242, 88, 117, 8, 80, 13, 224, 120, 56, 19, 165, 190, 45, 124, 193, 38, 185, 40, 115, 222, 149, 243, 106, 169, 170, 55, 79, 87, 59, 203, 110, 62, 3, 100, 130, 147, 171, 79, 9, 227, 36, 39, 134, 231, 253, 179, 7, 23, 19, 206, 64, 56, 32, 175, 127, 46, 234, 192, 238, 190, 122, 35, 52, 162, 176, 223, 174, 205, 233, 88, 60, 32, 77, 154, 144, 15, 148, 126, 78, 53, 37, 247, 129, 104, 215, 9, 0, 43, 198, 64, 47, 115, 102, 232, 31, 49, 70, 219, 23, 54, 242, 73, 110, 145, 16, 206, 59, 91, 179, 172, 134, 143, 20, 22, 78, 167, 102, 59, 91, 29, 206, 197, 73, 245, 245, 219, 59, 42, 198, 105, 195, 50, 23, 196, 133, 185, 8, 30, 197, 92, 213, 48, 194, 217, 133, 65, 229, 16, 70, 231, 232, 230, 167, 77, 127, 233, 148, 87, 181, 62, 1, 159, 32, 27, 110, 167, 31, 111, 1, 196, 242, 7, 123, 241, 89, 221, 132, 252, 185, 175, 112, 51, 108, 228, 189, 207, 189, 89, 23, 65, 205, 7, 53, 171, 220, 245, 242, 85, 187, 55, 153, 8, 255, 1, 171, 160, 187, 109, 130, 245, 4, 26, 1, 83, 223, 10, 221, 61, 65, 101, 0, 94, 136, 47, 2, 209, 7, 143, 65, 216, 32, 71, 168, 155, 198, 236, 170, 205, 64, 49, 129, 205, 48, 19, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 21, 49, 6, 4, 4, 1, 0, 0, 0, 48, 87, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 20, 49, 74, 30, 72, 0, 49, 0, 99, 0, 57, 0, 48, 0, 53, 0, 48, 0, 51, 0, 97, 0, 45, 0, 55, 0, 56, 0, 52, 0, 57, 0, 45, 0, 52, 0, 51, 0, 54, 0, 99, 0, 45, 0, 56, 0, 54, 0, 97, 0, 52, 0, 45, 0, 49, 0, 51, 0, 50, 0, 97, 0, 54, 0, 53, 0, 100, 0, 52, 0, 97, 0, 48, 0, 48, 0, 55, 48, 93, 6, 9, 43, 6, 1, 4, 1, 130, 55, 17, 1, 49, 80, 30, 78, 0, 77, 0, 105, 0, 99, 0, 114, 0, 111, 0, 115, 0, 111, 0, 102, 0, 116, 0, 32, 0, 83, 0, 116, 0, 114, 0, 111, 0, 110, 0, 103, 0, 32, 0, 67, 0, 114, 0, 121, 0, 112, 0, 116, 0, 111, 0, 103, 0, 114, 0, 97, 0, 112, 0, 104, 0, 105, 0, 99, 0, 32, 0, 80, 0, 114, 0, 111, 0, 118, 0, 105, 0, 100, 0, 101, 0, 114, 48, 130, 3, 207, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 6, 160, 130, 3, 192, 48, 130, 3, 188, 2, 1, 0, 48, 130, 3, 181, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 48, 28, 6, 10, 42, 134, 72, 134, 247, 13, 1, 12, 1, 3, 48, 14, 4, 8, 200, 59, 127, 243, 171, 147, 157, 127, 2, 2, 7, 208, 128, 130, 3, 136, 99, 75, 113, 248, 97, 190, 186, 177, 98, 176, 186, 91, 120, 170, 132, 193, 62, 121, 23, 129, 255, 220, 247, 43, 159, 29, 163, 114, 13, 93, 143, 171, 102, 66, 252, 74, 99, 253, 38, 53, 56, 73, 69, 51, 98, 128, 200, 232, 11, 162, 4, 170, 137, 109, 228, 185, 27, 6, 195, 120, 253, 174, 180, 67, 183, 248, 105, 101, 6, 173, 66, 251, 129, 249, 90, 140, 192, 43, 14, 201, 78, 111, 140, 115, 138, 216, 199, 45, 66, 89, 55, 144, 91, 37, 72, 194, 78, 193, 34, 16, 13, 88, 91, 183, 96, 143, 202, 15, 164, 215, 97, 222, 124, 248, 92, 221, 184, 103, 54, 132, 64, 114, 209, 116, 102, 168, 236, 8, 132, 143, 98, 218, 86, 110, 79, 45, 73, 156, 55, 35, 162, 83, 219, 200, 223, 144, 56, 64, 170, 137, 193, 157, 107, 47, 58, 38, 128, 142, 18, 184, 44, 80, 182, 80, 26, 8, 203, 161, 224, 254, 247, 223, 56, 124, 23, 187, 123, 63, 254, 147, 193, 77, 209, 101, 76, 46, 70, 118, 57, 172, 177, 35, 15, 49, 189, 115, 28, 117, 133, 166, 225, 246, 19, 133, 195, 70, 30, 237, 25, 215, 141, 19, 115, 48, 104, 39, 41, 140, 104, 38, 32, 215, 222, 153, 232, 74, 16, 23, 233, 21, 237, 145, 161, 62, 225, 143, 30, 219, 183, 12, 62, 24, 141, 28, 73, 49, 203, 92, 39, 133, 152, 143, 176, 197, 87, 47, 128, 228, 128, 169, 5, 121, 168, 15, 141, 217, 179, 79, 128, 150, 131, 78, 224, 238, 184, 141, 249, 123, 211, 241, 236, 103, 45, 7, 211, 163, 108, 216, 104, 200, 208, 134, 78, 196, 96, 118, 184, 78, 62, 88, 221, 5, 155, 249, 176, 207, 238, 2, 147, 229, 18, 77, 69, 189, 13, 237, 212, 224, 12, 61, 45, 254, 130, 135, 234, 41, 51, 142, 94, 24, 174, 55, 247, 56, 115, 206, 214, 161, 85, 51, 74, 248, 16, 219, 70, 91, 82, 26, 150, 59, 215, 39, 41, 85, 211, 77, 49, 23, 52, 59, 127, 20, 22, 130, 40, 253, 112, 123, 6, 15, 253, 98, 212, 24, 154, 126, 134, 216, 76, 76, 110, 212, 70, 62, 213, 115, 53, 158, 209, 152, 92, 178, 254, 24, 224, 121, 208, 19, 114, 250, 255, 80, 163, 61, 237, 48, 108, 51, 36, 41, 84, 97, 175, 131, 106, 150, 73, 176, 67, 114, 173, 244, 158, 235, 127, 19, 85, 179, 74, 144, 206, 126, 50, 39, 173, 112, 232, 108, 43, 9, 193, 167, 162, 63, 242, 218, 47, 26, 121, 237, 13, 240, 7, 107, 193, 202, 117, 224, 208, 128, 174, 137, 242, 221, 209, 37, 88, 184, 254, 69, 10, 39, 163, 158, 55, 249, 111, 238, 9, 78, 154, 184, 139, 227, 198, 39, 127, 222, 220, 77, 101, 128, 31, 210, 138, 48, 36, 26, 80, 160, 167, 181, 185, 196, 195, 208, 129, 23, 17, 157, 101, 157, 45, 173, 249, 158, 173, 245, 103, 88, 24, 170, 174, 203, 92, 69, 211, 167, 1, 221, 73, 99, 16, 148, 44, 97, 190, 28, 144, 252, 87, 10, 13, 95, 83, 211, 86, 14, 176, 27, 77, 236, 68, 10, 245, 185, 82, 154, 22, 207, 1, 163, 21, 214, 183, 201, 254, 91, 176, 171, 176, 223, 193, 128, 33, 100, 34, 32, 146, 156, 207, 247, 97, 250, 244, 41, 140, 253, 230, 56, 62, 90, 190, 41, 225, 50, 221, 134, 219, 63, 4, 81, 210, 106, 154, 6, 186, 97, 227, 13, 79, 111, 18, 204, 184, 246, 107, 194, 105, 65, 30, 179, 125, 196, 248, 96, 216, 77, 160, 27, 93, 42, 23, 6, 246, 13, 178, 22, 46, 67, 83, 224, 116, 91, 136, 223, 246, 213, 250, 207, 252, 197, 217, 139, 118, 245, 91, 236, 66, 208, 138, 154, 75, 107, 109, 151, 202, 68, 240, 212, 67, 20, 99, 109, 248, 139, 224, 206, 84, 152, 224, 170, 51, 137, 117, 64, 222, 91, 239, 71, 72, 162, 247, 243, 187, 164, 53, 123, 99, 186, 208, 39, 12, 160, 61, 78, 19, 205, 15, 250, 185, 238, 102, 144, 146, 127, 94, 250, 121, 240, 122, 240, 156, 182, 119, 143, 16, 120, 138, 161, 170, 79, 93, 169, 100, 149, 168, 76, 173, 8, 163, 172, 89, 153, 86, 73, 117, 100, 123, 115, 69, 135, 50, 29, 33, 66, 82, 71, 118, 231, 185, 151, 133, 179, 56, 251, 225, 76, 36, 189, 43, 39, 202, 168, 46, 25, 181, 127, 109, 78, 121, 80, 82, 137, 32, 196, 116, 100, 197, 0, 222, 88, 92, 165, 89, 146, 146, 239, 137, 28, 164, 44, 27, 162, 74, 37, 195, 54, 78, 18, 210, 106, 39, 162, 157, 145, 228, 70, 101, 195, 128, 222, 235, 117, 238, 29, 11, 180, 80, 104, 240, 118, 44, 231, 98, 239, 211, 64, 167, 59, 209, 26, 103, 168, 95, 187, 243, 178, 55, 31, 26, 230, 135, 67, 205, 232, 23, 89, 177, 223, 32, 105, 80, 186, 195, 82, 86, 144, 216, 110, 222, 223, 91, 20, 100, 73, 139, 128, 153, 150, 210, 140, 18, 197, 42, 121, 160, 77, 194, 37, 252, 68, 94, 32, 28, 245, 145, 29, 232, 182, 213, 246, 68, 175, 48, 59, 48, 31, 48, 7, 6, 5, 43, 14, 3, 2, 26, 4, 20, 121, 31, 55, 196, 109, 33, 122, 128, 32, 212, 84, 89, 7, 193, 97, 193, 144, 148, 24, 59, 4, 20, 161, 35, 154, 3, 112, 148, 126, 41, 44, 75, 51, 43, 182, 209, 49, 49, 210, 122, 30, 103, 2, 2, 7, 208 }; - - public X509Certificate2 certificate1 = X509CertificateLoader.LoadPkcs12(certificateRawBytes1, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.UserKeySet); - public X509Certificate2 certificate2 = X509CertificateLoader.LoadPkcs12(certificateRawBytes2, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.UserKeySet); - public X509Certificate2 certificate3 = X509CertificateLoader.LoadPkcs12(certificateRawBytes3, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.UserKeySet); - - public CertificateFixture() - { - AddCertificateToStore(certificate1, StoreLocation.CurrentUser); - AddCertificateToStore(certificate2, StoreLocation.CurrentUser); - AddCertificateToStore(certificate3, StoreLocation.CurrentUser); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && IsAdmin) - { - AddCertificateToStore(certificate3, StoreLocation.LocalMachine); - } - } - - public void Dispose() - { - // Do Not remove Certificates to provide concurrency support - // RemoveCertificate(certificate1, StoreLocation.CurrentUser); - // RemoveCertificate(certificate2, StoreLocation.CurrentUser); - } - - public static void AddCertificateToStore(X509Certificate2 certificate, StoreLocation certificateStoreLocation) - { - X509Store certStore = null; - try - { - certStore = new X509Store(StoreName.My, certificateStoreLocation); - certStore.Open(OpenFlags.ReadWrite); - if (!certStore.Certificates.Contains(certificate)) - { - certStore.Add(certificate); - } - - } - finally - { - if (certStore != null) - { - certStore.Close(); - } - } - } - - public static void RemoveCertificate(X509Certificate2 certificate, StoreLocation certificateStoreLocation) - { - X509Store certStore = null; - try - { - certStore = new X509Store(StoreName.My, certificateStoreLocation); - certStore.Open(OpenFlags.ReadWrite); - certStore.Remove(certificate); - } - finally - { - if (certStore != null) - { - certStore.Close(); - } - } - } - } } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs index dd2f122772..bed6604fd4 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AlwaysEncryptedTests/Utility.cs @@ -177,83 +177,6 @@ internal static byte[] GenerateInvalidEncryptedCek(byte[] encryptedCek, ECEKCorr return cipherText; } - internal static X509Certificate2 CreateCertificate() - { - byte[] certificateRawBytes = new byte[] { 48, 130, 10, 44, 2, 1, 3, 48, 130, 9, 232, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 160, 130, 9, 217, 4, 130, 9, 213, 48, 130, 9, 209, 48, 130, 5, 250, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 160, 130, 5, 235, 4, 130, 5, 231, 48, 130, 5, 227, 48, 130, 5, 223, 6, 11, 42, 134, 72, 134, 247, 13, 1, 12, 10, 1, 2, 160, 130, 4, 254, 48, 130, 4, 250, 48, 28, 6, 10, 42, 134, 72, 134, 247, 13, 1, 12, 1, 3, 48, 14, 4, 8, 16, 138, 0, 169, 73, 31, 210, 173, 2, 2, 7, 208, 4, 130, 4, 216, 188, 205, 213, 250, 205, 254, 228, 160, 194, 177, 32, 195, 5, 154, 111, 7, 166, 229, 222, 46, 194, 101, 201, 219, 151, 206, 102, 223, 17, 34, 182, 108, 227, 197, 225, 244, 226, 110, 218, 105, 148, 127, 131, 47, 1, 248, 184, 57, 233, 144, 8, 209, 163, 228, 250, 131, 33, 99, 212, 251, 175, 116, 23, 185, 105, 134, 105, 133, 247, 194, 195, 32, 176, 171, 54, 9, 212, 143, 255, 82, 12, 134, 0, 193, 144, 160, 181, 185, 12, 153, 12, 240, 134, 1, 51, 64, 97, 16, 191, 173, 179, 231, 90, 199, 88, 228, 255, 244, 197, 84, 166, 146, 212, 230, 188, 167, 44, 165, 246, 112, 252, 1, 86, 204, 221, 151, 44, 128, 17, 243, 133, 75, 190, 254, 221, 85, 236, 174, 87, 250, 164, 4, 241, 198, 35, 120, 230, 127, 215, 93, 54, 40, 149, 88, 25, 64, 13, 34, 142, 193, 43, 76, 62, 73, 233, 216, 175, 253, 30, 179, 127, 164, 246, 30, 181, 10, 96, 95, 131, 170, 112, 111, 91, 61, 184, 1, 137, 249, 236, 41, 169, 120, 107, 172, 148, 122, 118, 15, 30, 198, 41, 130, 3, 175, 253, 197, 82, 218, 171, 26, 35, 129, 95, 202, 95, 144, 148, 40, 42, 120, 94, 111, 224, 51, 149, 95, 115, 29, 19, 223, 117, 123, 17, 66, 217, 112, 121, 167, 147, 250, 169, 25, 79, 145, 251, 187, 217, 38, 200, 86, 65, 181, 138, 22, 137, 42, 11, 141, 9, 169, 213, 177, 141, 86, 44, 193, 110, 143, 0, 46, 122, 198, 168, 75, 196, 85, 231, 95, 26, 242, 180, 162, 250, 69, 184, 95, 118, 210, 201, 31, 166, 166, 92, 106, 174, 246, 179, 180, 160, 251, 94, 101, 134, 18, 204, 120, 211, 38, 217, 44, 40, 176, 207, 229, 123, 68, 11, 159, 47, 129, 193, 37, 172, 107, 166, 27, 139, 49, 196, 89, 63, 210, 197, 186, 140, 94, 70, 180, 5, 174, 28, 51, 229, 10, 150, 161, 250, 137, 64, 205, 168, 1, 228, 198, 223, 200, 37, 169, 189, 189, 84, 187, 11, 103, 30, 245, 253, 101, 62, 98, 246, 127, 234, 24, 74, 217, 163, 88, 63, 165, 220, 208, 255, 127, 174, 173, 122, 202, 7, 50, 180, 120, 32, 112, 52, 165, 76, 142, 211, 248, 80, 91, 152, 92, 208, 100, 210, 156, 148, 150, 67, 203, 43, 136, 176, 89, 139, 143, 51, 30, 29, 57, 127, 242, 193, 187, 185, 80, 189, 228, 55, 144, 214, 194, 47, 49, 107, 222, 189, 242, 24, 125, 137, 159, 212, 127, 167, 104, 47, 141, 38, 196, 191, 190, 50, 65, 14, 140, 50, 254, 188, 33, 20, 202, 13, 0, 38, 130, 1, 80, 31, 48, 30, 190, 67, 84, 147, 133, 229, 137, 43, 147, 205, 254, 6, 187, 45, 11, 114, 77, 241, 108, 109, 112, 220, 200, 128, 76, 101, 201, 238, 19, 32, 210, 196, 61, 89, 133, 236, 175, 118, 214, 171, 240, 197, 92, 29, 81, 106, 36, 200, 131, 120, 114, 103, 24, 168, 206, 70, 165, 226, 237, 115, 27, 156, 94, 95, 74, 42, 43, 61, 139, 156, 165, 30, 197, 212, 187, 122, 60, 201, 221, 201, 32, 80, 64, 105, 29, 225, 126, 218, 179, 168, 82, 177, 226, 63, 244, 130, 106, 128, 50, 229, 187, 117, 83, 90, 157, 193, 163, 13, 230, 107, 142, 243, 33, 153, 142, 161, 81, 83, 137, 220, 191, 121, 222, 85, 254, 85, 247, 212, 98, 181, 255, 175, 228, 162, 235, 92, 70, 7, 253, 210, 84, 125, 229, 220, 19, 33, 120, 156, 160, 55, 144, 196, 109, 235, 166, 206, 99, 238, 97, 251, 163, 207, 81, 125, 161, 201, 150, 91, 227, 171, 247, 225, 93, 166, 105, 157, 145, 13, 244, 8, 214, 233, 193, 27, 108, 156, 206, 211, 28, 216, 13, 28, 42, 185, 251, 55, 156, 12, 67, 172, 195, 174, 96, 70, 127, 74, 236, 170, 146, 65, 44, 79, 219, 197, 166, 227, 101, 16, 160, 72, 43, 127, 106, 108, 13, 174, 138, 111, 67, 192, 185, 36, 82, 25, 253, 40, 211, 171, 246, 219, 14, 64, 125, 91, 150, 173, 114, 100, 210, 29, 202, 39, 102, 81, 0, 90, 176, 122, 149, 121, 254, 53, 130, 202, 107, 233, 131, 47, 216, 31, 66, 96, 55, 37, 164, 195, 217, 205, 153, 74, 83, 169, 167, 241, 51, 102, 140, 102, 202, 15, 57, 193, 172, 140, 114, 115, 218, 156, 111, 238, 162, 48, 44, 141, 156, 15, 65, 29, 242, 187, 73, 19, 192, 102, 69, 192, 172, 16, 197, 17, 104, 68, 69, 224, 180, 252, 227, 80, 43, 148, 126, 85, 12, 168, 85, 18, 146, 90, 37, 215, 123, 169, 117, 87, 125, 228, 235, 11, 163, 132, 239, 31, 163, 196, 121, 19, 217, 59, 35, 52, 125, 204, 246, 142, 176, 137, 170, 76, 50, 37, 29, 250, 82, 145, 113, 138, 161, 9, 186, 227, 151, 40, 57, 217, 78, 39, 154, 237, 20, 102, 184, 78, 141, 194, 196, 22, 171, 135, 7, 62, 236, 163, 34, 222, 172, 186, 230, 175, 36, 243, 249, 13, 95, 15, 77, 227, 222, 208, 12, 140, 103, 111, 26, 160, 237, 27, 158, 162, 189, 226, 155, 76, 135, 220, 56, 152, 230, 151, 73, 120, 68, 83, 140, 238, 6, 63, 130, 182, 12, 33, 181, 201, 242, 36, 236, 9, 160, 237, 144, 22, 228, 17, 201, 45, 25, 84, 96, 127, 51, 178, 181, 173, 59, 2, 219, 37, 244, 75, 16, 135, 51, 67, 69, 240, 191, 232, 122, 200, 191, 220, 111, 18, 163, 179, 201, 101, 246, 105, 175, 241, 47, 187, 156, 251, 173, 122, 116, 51, 100, 93, 219, 166, 160, 240, 181, 161, 220, 15, 218, 215, 46, 62, 81, 79, 238, 38, 51, 115, 69, 169, 32, 252, 118, 89, 25, 208, 126, 143, 6, 135, 122, 179, 25, 4, 183, 177, 61, 62, 160, 115, 38, 184, 109, 213, 185, 14, 177, 242, 23, 228, 4, 204, 7, 199, 62, 50, 18, 5, 124, 140, 105, 149, 63, 89, 37, 117, 145, 26, 105, 83, 13, 2, 113, 211, 171, 208, 10, 25, 177, 42, 220, 4, 153, 109, 106, 99, 75, 97, 14, 42, 168, 164, 130, 88, 228, 167, 129, 198, 121, 135, 103, 231, 101, 208, 35, 108, 249, 151, 187, 74, 31, 59, 68, 127, 34, 117, 150, 179, 229, 65, 236, 169, 16, 12, 170, 67, 61, 210, 228, 72, 121, 169, 206, 63, 71, 142, 47, 16, 117, 59, 205, 159, 50, 14, 19, 111, 171, 196, 117, 113, 200, 239, 112, 175, 147, 115, 203, 37, 241, 12, 145, 111, 160, 168, 234, 240, 108, 235, 136, 143, 179, 240, 57, 74, 49, 82, 171, 35, 157, 240, 125, 116, 238, 36, 65, 225, 197, 138, 53, 32, 85, 247, 115, 154, 193, 145, 153, 176, 232, 43, 89, 96, 221, 238, 105, 42, 205, 59, 52, 97, 199, 228, 207, 23, 55, 22, 44, 27, 112, 74, 230, 228, 228, 214, 106, 91, 42, 34, 239, 156, 103, 151, 106, 30, 2, 0, 103, 16, 130, 106, 128, 117, 120, 101, 107, 206, 52, 201, 116, 168, 27, 185, 6, 181, 161, 116, 108, 49, 129, 205, 48, 19, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 21, 49, 6, 4, 4, 1, 0, 0, 0, 48, 87, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 20, 49, 74, 30, 72, 0, 53, 0, 54, 0, 102, 0, 54, 0, 54, 0, 53, 0, 97, 0, 50, 0, 45, 0, 50, 0, 54, 0, 50, 0, 98, 0, 45, 0, 52, 0, 50, 0, 52, 0, 50, 0, 45, 0, 57, 0, 57, 0, 97, 0, 56, 0, 45, 0, 57, 0, 55, 0, 55, 0, 97, 0, 97, 0, 57, 0, 56, 0, 99, 0, 101, 0, 97, 0, 97, 0, 100, 48, 93, 6, 9, 43, 6, 1, 4, 1, 130, 55, 17, 1, 49, 80, 30, 78, 0, 77, 0, 105, 0, 99, 0, 114, 0, 111, 0, 115, 0, 111, 0, 102, 0, 116, 0, 32, 0, 83, 0, 116, 0, 114, 0, 111, 0, 110, 0, 103, 0, 32, 0, 67, 0, 114, 0, 121, 0, 112, 0, 116, 0, 111, 0, 103, 0, 114, 0, 97, 0, 112, 0, 104, 0, 105, 0, 99, 0, 32, 0, 80, 0, 114, 0, 111, 0, 118, 0, 105, 0, 100, 0, 101, 0, 114, 48, 130, 3, 207, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 6, 160, 130, 3, 192, 48, 130, 3, 188, 2, 1, 0, 48, 130, 3, 181, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 48, 28, 6, 10, 42, 134, 72, 134, 247, 13, 1, 12, 1, 3, 48, 14, 4, 8, 249, 68, 23, 15, 90, 178, 55, 11, 2, 2, 7, 208, 128, 130, 3, 136, 189, 232, 130, 97, 198, 137, 5, 230, 136, 106, 215, 76, 90, 0, 66, 64, 19, 132, 179, 239, 204, 147, 171, 145, 124, 195, 15, 246, 30, 203, 38, 201, 2, 161, 7, 62, 68, 229, 251, 178, 182, 14, 196, 8, 147, 127, 74, 211, 9, 178, 219, 14, 204, 237, 59, 181, 16, 54, 199, 106, 231, 162, 42, 124, 144, 191, 249, 104, 251, 199, 30, 96, 66, 145, 83, 140, 195, 197, 220, 166, 143, 255, 193, 218, 88, 87, 135, 11, 52, 156, 122, 252, 178, 19, 207, 151, 228, 191, 226, 81, 208, 208, 101, 148, 249, 166, 242, 70, 140, 39, 180, 152, 3, 29, 150, 23, 212, 89, 209, 32, 134, 105, 128, 10, 211, 220, 4, 161, 38, 185, 202, 109, 119, 177, 29, 133, 55, 7, 126, 40, 120, 195, 36, 134, 119, 242, 130, 142, 142, 112, 212, 116, 6, 91, 150, 197, 164, 1, 53, 172, 139, 47, 230, 29, 254, 53, 106, 18, 184, 87, 117, 249, 108, 226, 61, 27, 133, 37, 250, 48, 235, 194, 115, 71, 244, 92, 165, 61, 6, 101, 153, 239, 167, 74, 230, 159, 26, 66, 132, 89, 191, 44, 73, 144, 174, 48, 219, 61, 205, 131, 107, 90, 66, 157, 148, 22, 19, 47, 182, 10, 141, 113, 225, 201, 47, 31, 244, 253, 125, 128, 244, 70, 200, 38, 123, 146, 102, 94, 247, 15, 45, 62, 176, 2, 225, 70, 17, 193, 129, 133, 121, 6, 96, 135, 138, 68, 105, 108, 186, 126, 253, 210, 80, 228, 168, 234, 202, 40, 160, 246, 117, 60, 119, 54, 126, 166, 137, 237, 94, 228, 241, 167, 170, 19, 215, 36, 154, 215, 179, 44, 35, 223, 181, 13, 55, 251, 122, 176, 132, 72, 213, 253, 103, 16, 174, 213, 35, 217, 188, 214, 113, 114, 212, 70, 106, 124, 54, 233, 93, 156, 7, 135, 253, 183, 174, 165, 29, 170, 23, 186, 148, 232, 114, 226, 82, 139, 224, 78, 239, 179, 155, 70, 117, 39, 188, 242, 198, 93, 230, 209, 169, 8, 179, 100, 136, 100, 181, 217, 240, 173, 97, 92, 176, 135, 193, 149, 89, 85, 201, 206, 191, 173, 168, 48, 47, 224, 233, 145, 47, 213, 32, 76, 94, 230, 86, 63, 85, 170, 211, 107, 94, 133, 144, 35, 102, 49, 253, 150, 120, 163, 246, 13, 105, 76, 211, 215, 244, 8, 121, 108, 225, 54, 95, 229, 239, 32, 203, 145, 0, 242, 84, 176, 22, 61, 79, 71, 84, 46, 31, 135, 71, 15, 192, 52, 250, 54, 78, 98, 45, 173, 191, 101, 161, 49, 178, 136, 67, 40, 70, 24, 148, 96, 253, 160, 1, 185, 209, 37, 159, 102, 116, 11, 252, 74, 28, 173, 218, 80, 149, 33, 189, 214, 186, 251, 97, 179, 199, 151, 48, 78, 211, 58, 241, 1, 223, 118, 99, 57, 91, 15, 142, 0, 227, 16, 18, 170, 22, 45, 230, 13, 199, 39, 143, 231, 134, 33, 117, 229, 224, 133, 130, 135, 8, 48, 170, 137, 72, 216, 34, 249, 110, 33, 25, 12, 244, 204, 66, 218, 68, 92, 144, 149, 247, 186, 92, 131, 158, 42, 9, 253, 9, 198, 27, 158, 83, 18, 151, 107, 37, 77, 251, 61, 93, 101, 60, 76, 110, 84, 167, 16, 88, 26, 119, 196, 177, 185, 172, 87, 70, 207, 167, 32, 80, 80, 9, 3, 201, 195, 48, 39, 56, 240, 184, 229, 83, 12, 80, 145, 25, 205, 226, 173, 207, 198, 206, 40, 23, 224, 74, 139, 112, 90, 17, 247, 83, 11, 18, 145, 111, 115, 250, 168, 219, 194, 96, 145, 184, 8, 22, 169, 242, 64, 166, 25, 103, 197, 186, 28, 49, 170, 10, 113, 135, 21, 220, 172, 234, 126, 39, 233, 11, 119, 235, 184, 38, 47, 44, 101, 99, 86, 210, 205, 205, 104, 98, 165, 182, 126, 124, 109, 150, 211, 79, 242, 110, 96, 74, 96, 173, 249, 63, 245, 52, 180, 148, 152, 117, 241, 218, 220, 75, 43, 96, 218, 132, 199, 248, 60, 62, 15, 49, 75, 62, 128, 31, 69, 2, 124, 250, 164, 60, 65, 127, 112, 197, 53, 157, 120, 148, 100, 146, 245, 132, 192, 192, 188, 64, 1, 142, 206, 132, 241, 210, 161, 126, 56, 5, 95, 204, 89, 53, 143, 224, 137, 126, 182, 71, 12, 49, 39, 211, 33, 48, 177, 234, 136, 205, 169, 53, 209, 141, 89, 219, 83, 102, 12, 43, 94, 96, 66, 2, 232, 131, 85, 182, 130, 222, 71, 121, 228, 246, 9, 41, 141, 95, 73, 70, 51, 58, 86, 168, 193, 76, 25, 196, 40, 203, 62, 139, 217, 178, 187, 87, 171, 212, 85, 42, 136, 145, 174, 134, 171, 113, 188, 28, 31, 4, 77, 87, 237, 194, 98, 20, 111, 165, 95, 45, 204, 150, 176, 85, 128, 75, 131, 175, 45, 197, 209, 224, 176, 179, 39, 216, 114, 150, 202, 79, 153, 178, 197, 176, 237, 84, 123, 207, 52, 250, 56, 55, 191, 20, 249, 173, 204, 8, 59, 199, 237, 23, 234, 158, 246, 203, 222, 105, 163, 152, 99, 137, 47, 112, 98, 79, 161, 88, 198, 125, 106, 174, 85, 134, 216, 35, 80, 161, 140, 177, 161, 154, 169, 80, 193, 224, 238, 238, 31, 92, 124, 238, 147, 162, 209, 186, 50, 48, 59, 48, 31, 48, 7, 6, 5, 43, 14, 3, 2, 26, 4, 20, 249, 117, 64, 150, 197, 135, 218, 207, 32, 100, 203, 75, 240, 98, 164, 185, 50, 202, 93, 125, 4, 20, 180, 36, 134, 220, 75, 81, 26, 153, 143, 72, 201, 209, 29, 87, 166, 59, 206, 207, 221, 99, 2, 2, 7, 208 }; - - X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12(certificateRawBytes, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.UserKeySet); - - X509Store certStore = null; - try - { - certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); - certStore.Open(OpenFlags.ReadWrite); - if (!certStore.Certificates.Contains(certificate)) - { - certStore.Add(certificate); - } - - } - finally - { - if (certStore != null) - { - certStore.Close(); - } - } - - return certificate; - } - - /// - /// Gets the certificate. - /// - /// - /// - /// - internal static X509Certificate2 GetCertificate(string certificateName, StoreLocation certificateStoreLocation) - { - Assert.True(!string.IsNullOrWhiteSpace(certificateName)); - X509Store certStore = null; - try - { - certStore = new X509Store(StoreName.My, certificateStoreLocation); - certStore.Open(OpenFlags.ReadOnly); - X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindBySubjectName, certificateName, validOnly: false); - Assert.True(certCollection != null && certCollection.Count > 0); - - return certCollection[0]; - } - finally - { - if (certStore != null) - { - certStore.Close(); - } - } - } - - /// - /// Removes a certificate from the local certificate store (useful for test cleanup). - /// - internal static void RemoveCertificate(X509Certificate2 certificate) - { - X509Store certStore = null; - try - { - certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); - certStore.Open(OpenFlags.ReadWrite); - certStore.Remove(certificate); - } - finally - { - if (certStore != null) - { - certStore.Close(); - } - } - } - /// /// Encrypt Data using AED /// @@ -352,37 +275,6 @@ internal static byte[] DecryptDataUsingAED(byte[] encryptedCellBlob, byte[] key, return decryptedData; } -#if NETFRAMEWORK - /// - /// Create a self-signed certificate without private key. - /// - internal static X509Certificate2 CreateCertificateWithNoPrivateKey() - { - byte[] certificateRawBytes = new byte[] { 48, 130, 10, 44, 2, 1, 3, 48, 130, 9, 232, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 160, 130, 9, 217, 4, 130, 9, 213, 48, 130, 9, 209, 48, 130, 5, 250, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 160, 130, 5, 235, 4, 130, 5, 231, 48, 130, 5, 227, 48, 130, 5, 223, 6, 11, 42, 134, 72, 134, 247, 13, 1, 12, 10, 1, 2, 160, 130, 4, 254, 48, 130, 4, 250, 48, 28, 6, 10, 42, 134, 72, 134, 247, 13, 1, 12, 1, 3, 48, 14, 4, 8, 235, 104, 8, 192, 48, 172, 84, 29, 2, 2, 7, 208, 4, 130, 4, 216, 223, 187, 54, 199, 219, 97, 167, 152, 236, 137, 175, 54, 77, 8, 247, 205, 172, 76, 84, 103, 8, 28, 176, 175, 188, 108, 135, 239, 218, 134, 129, 181, 251, 107, 39, 184, 253, 101, 94, 26, 232, 8, 233, 161, 112, 129, 228, 7, 130, 121, 197, 85, 170, 39, 8, 195, 112, 127, 125, 148, 97, 162, 52, 74, 56, 187, 228, 232, 17, 145, 186, 138, 140, 245, 76, 203, 154, 41, 148, 15, 86, 152, 239, 221, 3, 64, 158, 137, 250, 33, 11, 23, 49, 250, 50, 116, 183, 138, 223, 230, 8, 210, 52, 95, 6, 238, 167, 153, 107, 99, 214, 58, 56, 70, 135, 6, 173, 190, 175, 116, 23, 53, 250, 166, 192, 128, 93, 243, 109, 60, 55, 10, 246, 188, 43, 56, 157, 116, 22, 105, 215, 194, 58, 229, 28, 93, 175, 65, 64, 162, 237, 182, 40, 159, 140, 24, 3, 226, 63, 246, 201, 144, 40, 128, 122, 15, 188, 130, 195, 120, 65, 191, 10, 164, 29, 119, 17, 60, 231, 63, 26, 172, 87, 191, 110, 233, 107, 44, 206, 197, 170, 176, 12, 6, 44, 181, 219, 56, 45, 10, 75, 145, 154, 148, 162, 169, 240, 109, 254, 115, 33, 81, 206, 88, 33, 91, 251, 235, 56, 56, 41, 75, 145, 36, 177, 104, 68, 7, 76, 150, 230, 182, 136, 239, 10, 21, 3, 10, 23, 217, 103, 148, 160, 114, 55, 122, 22, 165, 91, 37, 230, 23, 28, 182, 107, 31, 53, 78, 226, 125, 17, 81, 223, 48, 36, 51, 101, 19, 79, 202, 221, 197, 11, 152, 253, 155, 142, 63, 107, 51, 11, 197, 55, 18, 8, 109, 173, 83, 125, 201, 167, 170, 152, 152, 26, 142, 29, 77, 174, 189, 241, 185, 239, 56, 207, 128, 203, 136, 203, 226, 29, 88, 97, 230, 119, 161, 6, 15, 231, 9, 250, 96, 129, 40, 35, 201, 28, 220, 237, 24, 88, 88, 222, 239, 108, 39, 35, 147, 76, 242, 209, 122, 4, 165, 91, 18, 78, 74, 40, 131, 194, 1, 105, 104, 103, 207, 198, 222, 100, 2, 188, 130, 224, 187, 243, 170, 192, 0, 169, 69, 155, 32, 48, 159, 164, 254, 29, 255, 197, 250, 118, 69, 2, 11, 47, 232, 157, 151, 17, 106, 211, 82, 15, 246, 22, 117, 90, 220, 129, 228, 91, 249, 18, 147, 125, 13, 51, 98, 235, 213, 145, 81, 77, 139, 23, 50, 35, 165, 13, 117, 71, 82, 166, 120, 103, 121, 72, 229, 3, 116, 65, 90, 125, 224, 121, 19, 136, 215, 57, 73, 247, 249, 174, 197, 234, 13, 58, 182, 24, 46, 21, 122, 193, 111, 47, 40, 207, 75, 224, 155, 163, 138, 130, 38, 204, 211, 149, 132, 249, 37, 66, 194, 83, 147, 6, 187, 113, 60, 129, 139, 197, 84, 60, 179, 253, 192, 124, 67, 60, 29, 149, 244, 114, 238, 71, 144, 139, 0, 104, 29, 100, 90, 137, 151, 31, 138, 3, 35, 96, 243, 130, 203, 200, 191, 212, 247, 137, 194, 183, 150, 53, 213, 108, 9, 30, 18, 204, 248, 30, 60, 132, 25, 12, 186, 64, 179, 130, 165, 141, 77, 4, 244, 166, 0, 197, 145, 66, 51, 17, 198, 181, 54, 63, 112, 195, 70, 11, 93, 122, 175, 136, 8, 156, 136, 165, 228, 22, 105, 107, 87, 160, 1, 140, 134, 166, 151, 91, 76, 15, 187, 197, 131, 67, 5, 51, 191, 23, 4, 105, 219, 167, 45, 167, 3, 118, 161, 54, 187, 250, 136, 201, 233, 148, 234, 228, 65, 18, 105, 92, 201, 5, 100, 213, 59, 97, 29, 163, 42, 50, 5, 59, 178, 122, 190, 159, 218, 10, 239, 183, 20, 226, 197, 187, 190, 160, 5, 122, 45, 70, 111, 205, 232, 160, 115, 145, 173, 255, 60, 105, 204, 253, 18, 212, 167, 23, 95, 10, 146, 175, 0, 137, 166, 220, 51, 203, 244, 13, 27, 51, 121, 159, 178, 20, 178, 43, 133, 182, 169, 234, 56, 205, 153, 170, 26, 138, 48, 84, 2, 20, 11, 141, 41, 76, 178, 76, 10, 30, 9, 242, 158, 59, 9, 109, 240, 185, 30, 199, 136, 167, 146, 202, 239, 253, 95, 61, 56, 16, 166, 163, 78, 75, 241, 228, 98, 198, 59, 113, 214, 77, 58, 177, 251, 132, 167, 137, 82, 119, 216, 157, 8, 37, 95, 43, 106, 140, 117, 166, 0, 111, 84, 45, 43, 22, 220, 109, 219, 30, 165, 252, 91, 3, 203, 165, 91, 22, 202, 91, 223, 194, 122, 238, 159, 25, 1, 254, 183, 4, 7, 96, 150, 253, 199, 92, 250, 143, 107, 77, 112, 133, 202, 126, 117, 128, 59, 124, 111, 174, 41, 92, 184, 247, 248, 44, 43, 148, 37, 193, 30, 110, 34, 190, 210, 37, 230, 182, 113, 130, 3, 65, 85, 90, 60, 0, 177, 78, 95, 251, 111, 91, 12, 27, 111, 119, 74, 117, 81, 162, 174, 33, 110, 63, 242, 31, 24, 11, 186, 174, 80, 52, 76, 184, 42, 199, 203, 245, 75, 97, 104, 12, 206, 133, 206, 36, 30, 105, 254, 233, 145, 29, 224, 62, 139, 143, 168, 181, 142, 247, 139, 240, 2, 220, 57, 221, 62, 133, 90, 209, 106, 69, 82, 89, 172, 134, 230, 129, 154, 88, 35, 126, 16, 43, 107, 12, 76, 67, 116, 66, 181, 251, 73, 157, 31, 196, 240, 237, 184, 92, 126, 182, 46, 66, 91, 56, 37, 75, 235, 200, 90, 129, 103, 80, 73, 246, 156, 160, 169, 212, 3, 57, 238, 17, 6, 244, 219, 106, 112, 96, 80, 204, 181, 173, 82, 238, 24, 36, 232, 84, 158, 135, 211, 35, 133, 141, 46, 48, 179, 174, 127, 34, 44, 45, 193, 241, 222, 10, 175, 76, 64, 39, 191, 63, 182, 25, 39, 105, 61, 35, 162, 89, 253, 189, 59, 159, 225, 142, 174, 166, 5, 56, 253, 106, 170, 190, 136, 207, 37, 233, 54, 131, 111, 118, 198, 83, 52, 86, 102, 14, 38, 26, 181, 42, 175, 131, 116, 0, 82, 25, 96, 191, 188, 196, 158, 132, 25, 0, 160, 125, 188, 236, 71, 221, 58, 71, 247, 35, 85, 68, 183, 64, 119, 247, 159, 185, 240, 9, 230, 184, 43, 116, 163, 91, 67, 244, 33, 243, 210, 190, 86, 127, 14, 38, 60, 19, 211, 182, 96, 77, 86, 116, 159, 173, 134, 39, 217, 77, 131, 85, 126, 145, 224, 120, 94, 233, 103, 254, 14, 92, 242, 69, 17, 17, 63, 94, 251, 195, 199, 194, 175, 94, 137, 82, 25, 234, 253, 89, 225, 46, 103, 131, 109, 12, 204, 188, 141, 173, 146, 124, 221, 144, 235, 188, 165, 141, 95, 224, 56, 58, 53, 149, 94, 77, 204, 101, 195, 127, 8, 86, 122, 190, 7, 214, 60, 154, 222, 229, 101, 12, 73, 149, 216, 6, 124, 223, 165, 65, 197, 217, 61, 174, 172, 84, 179, 169, 153, 116, 47, 176, 76, 119, 232, 236, 44, 82, 146, 241, 136, 223, 251, 249, 12, 40, 216, 133, 54, 145, 43, 43, 135, 238, 2, 212, 216, 242, 118, 199, 195, 221, 16, 46, 29, 4, 95, 66, 58, 168, 47, 0, 11, 161, 15, 104, 189, 76, 245, 195, 254, 129, 123, 98, 1, 127, 230, 47, 171, 184, 87, 192, 241, 169, 219, 49, 129, 205, 48, 19, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 21, 49, 6, 4, 4, 1, 0, 0, 0, 48, 87, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 20, 49, 74, 30, 72, 0, 48, 0, 55, 0, 101, 0, 97, 0, 50, 0, 102, 0, 48, 0, 54, 0, 45, 0, 53, 0, 48, 0, 99, 0, 55, 0, 45, 0, 52, 0, 55, 0, 50, 0, 57, 0, 45, 0, 98, 0, 48, 0, 53, 0, 100, 0, 45, 0, 101, 0, 54, 0, 49, 0, 52, 0, 50, 0, 54, 0, 56, 0, 97, 0, 57, 0, 51, 0, 54, 0, 49, 48, 93, 6, 9, 43, 6, 1, 4, 1, 130, 55, 17, 1, 49, 80, 30, 78, 0, 77, 0, 105, 0, 99, 0, 114, 0, 111, 0, 115, 0, 111, 0, 102, 0, 116, 0, 32, 0, 83, 0, 116, 0, 114, 0, 111, 0, 110, 0, 103, 0, 32, 0, 67, 0, 114, 0, 121, 0, 112, 0, 116, 0, 111, 0, 103, 0, 114, 0, 97, 0, 112, 0, 104, 0, 105, 0, 99, 0, 32, 0, 80, 0, 114, 0, 111, 0, 118, 0, 105, 0, 100, 0, 101, 0, 114, 48, 130, 3, 207, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 6, 160, 130, 3, 192, 48, 130, 3, 188, 2, 1, 0, 48, 130, 3, 181, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 48, 28, 6, 10, 42, 134, 72, 134, 247, 13, 1, 12, 1, 3, 48, 14, 4, 8, 37, 194, 101, 21, 13, 36, 244, 253, 2, 2, 7, 208, 128, 130, 3, 136, 10, 38, 67, 113, 151, 160, 181, 156, 252, 50, 128, 39, 35, 98, 152, 133, 235, 238, 94, 73, 70, 252, 148, 94, 227, 150, 143, 176, 164, 232, 41, 137, 239, 196, 102, 6, 78, 134, 9, 254, 13, 200, 191, 171, 47, 166, 148, 30, 196, 230, 95, 126, 29, 42, 214, 201, 165, 49, 93, 149, 12, 7, 79, 167, 112, 237, 36, 142, 123, 246, 14, 212, 16, 78, 179, 106, 60, 251, 106, 13, 232, 222, 214, 255, 212, 48, 140, 91, 26, 201, 136, 119, 148, 0, 246, 63, 252, 9, 41, 63, 147, 198, 201, 26, 134, 126, 59, 103, 212, 103, 101, 47, 138, 137, 237, 190, 83, 123, 231, 194, 213, 147, 117, 116, 148, 170, 194, 12, 62, 100, 16, 254, 76, 65, 96, 126, 45, 221, 126, 161, 233, 194, 187, 117, 13, 201, 85, 26, 223, 13, 143, 147, 250, 64, 5, 85, 232, 165, 154, 77, 255, 192, 31, 166, 154, 251, 192, 199, 100, 220, 4, 79, 242, 191, 66, 134, 203, 50, 2, 105, 98, 247, 73, 66, 41, 179, 173, 177, 217, 196, 180, 48, 219, 79, 88, 154, 40, 249, 150, 169, 231, 215, 186, 61, 129, 223, 57, 84, 48, 245, 158, 161, 89, 204, 127, 155, 134, 155, 158, 208, 159, 245, 83, 5, 63, 188, 233, 164, 60, 38, 91, 255, 221, 6, 28, 107, 85, 188, 81, 114, 23, 143, 4, 78, 17, 178, 36, 44, 209, 7, 251, 78, 13, 35, 73, 243, 151, 150, 122, 161, 86, 52, 233, 148, 241, 144, 175, 230, 22, 97, 21, 229, 143, 172, 67, 12, 130, 254, 4, 144, 136, 20, 93, 161, 177, 249, 160, 58, 18, 135, 249, 107, 142, 116, 63, 228, 37, 105, 81, 121, 43, 107, 88, 166, 7, 59, 42, 139, 76, 71, 191, 137, 102, 185, 95, 166, 229, 23, 110, 123, 49, 239, 48, 183, 246, 102, 120, 28, 25, 39, 211, 183, 1, 201, 199, 158, 143, 25, 1, 165, 163, 99, 161, 237, 235, 148, 209, 180, 152, 111, 128, 40, 102, 90, 5, 228, 15, 244, 148, 33, 110, 153, 4, 159, 28, 241, 252, 117, 69, 165, 37, 129, 227, 151, 31, 191, 105, 106, 179, 87, 41, 37, 63, 18, 59, 198, 182, 91, 21, 41, 242, 237, 63, 240, 163, 110, 219, 94, 119, 28, 234, 70, 208, 56, 141, 163, 80, 4, 223, 110, 64, 161, 81, 82, 191, 67, 13, 95, 208, 122, 152, 8, 87, 197, 31, 141, 22, 161, 23, 211, 83, 222, 116, 234, 57, 228, 203, 122, 87, 146, 161, 167, 87, 126, 181, 34, 85, 90, 190, 30, 253, 188, 207, 205, 203, 11, 248, 56, 202, 107, 28, 106, 8, 247, 65, 91, 12, 123, 2, 252, 134, 153, 88, 146, 154, 99, 116, 103, 196, 40, 212, 197, 79, 63, 1, 241, 152, 35, 34, 84, 217, 128, 56, 0, 139, 218, 77, 22, 63, 120, 204, 192, 144, 152, 127, 60, 84, 143, 30, 203, 24, 78, 44, 24, 194, 71, 139, 34, 203, 212, 4, 216, 128, 29, 158, 142, 82, 147, 165, 250, 107, 222, 76, 152, 120, 21, 236, 240, 18, 167, 99, 97, 31, 104, 3, 134, 222, 185, 129, 130, 214, 90, 200, 254, 51, 86, 225, 209, 2, 224, 32, 38, 218, 77, 156, 102, 9, 158, 232, 155, 239, 33, 76, 222, 93, 105, 50, 72, 175, 220, 219, 17, 58, 147, 86, 107, 144, 35, 191, 186, 197, 218, 74, 71, 154, 117, 192, 247, 43, 176, 141, 82, 95, 21, 245, 199, 194, 20, 204, 111, 141, 183, 50, 22, 155, 54, 47, 164, 247, 33, 110, 208, 216, 123, 141, 209, 182, 101, 100, 140, 162, 83, 12, 12, 196, 113, 61, 119, 254, 184, 94, 78, 66, 72, 239, 124, 123, 48, 101, 162, 225, 175, 235, 100, 97, 71, 192, 254, 7, 234, 235, 94, 38, 241, 159, 96, 208, 128, 93, 68, 24, 69, 62, 236, 128, 155, 9, 56, 163, 236, 112, 90, 118, 11, 97, 33, 216, 89, 24, 127, 35, 5, 33, 103, 35, 27, 182, 249, 222, 74, 44, 243, 185, 177, 97, 145, 55, 113, 57, 186, 104, 128, 158, 1, 27, 182, 134, 158, 198, 228, 122, 149, 27, 185, 181, 248, 4, 98, 35, 113, 190, 228, 37, 84, 50, 250, 197, 180, 22, 103, 231, 136, 157, 96, 109, 205, 98, 195, 5, 146, 122, 238, 143, 155, 9, 245, 188, 30, 103, 55, 77, 1, 152, 207, 166, 218, 93, 237, 66, 182, 168, 31, 61, 111, 223, 189, 129, 118, 204, 121, 213, 212, 158, 159, 146, 227, 16, 63, 15, 25, 114, 72, 243, 3, 112, 217, 85, 194, 233, 211, 154, 178, 223, 170, 210, 215, 151, 146, 76, 212, 251, 234, 136, 23, 22, 156, 135, 40, 174, 163, 211, 154, 205, 237, 225, 86, 207, 195, 154, 170, 213, 33, 227, 75, 216, 234, 208, 159, 157, 48, 193, 243, 57, 79, 40, 187, 12, 147, 134, 150, 43, 169, 156, 208, 162, 94, 28, 192, 139, 133, 6, 112, 17, 245, 56, 161, 19, 254, 220, 63, 9, 58, 90, 144, 194, 186, 220, 166, 125, 179, 149, 46, 9, 18, 62, 244, 56, 232, 171, 16, 210, 106, 149, 170, 49, 173, 44, 50, 80, 108, 61, 151, 199, 86, 48, 59, 48, 31, 48, 7, 6, 5, 43, 14, 3, 2, 26, 4, 20, 171, 133, 31, 192, 88, 19, 36, 185, 245, 48, 81, 100, 39, 120, 104, 220, 55, 66, 79, 62, 4, 20, 87, 234, 127, 133, 228, 52, 169, 111, 27, 106, 183, 211, 251, 229, 188, 99, 150, 210, 181, 175, 2, 2, 7, 208 }; - X509Certificate2 certificate = new X509Certificate2(certificateRawBytes, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.UserKeySet); - X509Store certStore = null; - try - { - certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); - certStore.Open(OpenFlags.ReadWrite); - certificate.PrivateKey = null; - if (!certStore.Certificates.Contains(certificate)) - { - certStore.Add(certificate); - } - } - finally - { - if (certStore != null) - { - certStore.Close(); - } - } - - return certificate; - } -#endif - /// /// Gets hex representation of byte array. /// input byte array diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AmbientTransactionFailureTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AmbientTransactionFailureTest.cs index ed70dbd07d..b5069f6197 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AmbientTransactionFailureTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/AmbientTransactionFailureTest.cs @@ -4,6 +4,7 @@ using System; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; using System.Transactions; using Xunit; @@ -75,7 +76,12 @@ public class AmbientTransactionFailureTest }; [ConditionalTheory(nameof(s_isNotArmProcess))] // https://github.com/dotnet/corefx/issues/21598 - [MemberData(nameof(ExceptionTestDataForSqlException))] + [MemberData( + nameof(ExceptionTestDataForSqlException), + // xUnit can't consistently serialize the data for this test, so we + // disable enumeration of the test data to avoid warnings on the + // console. + DisableDiscoveryEnumeration = true)] public void TestSqlException(Action connectAction, string connectionString) { Assert.Throws(() => diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs index e6a6b9c73f..30896e545e 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/LocalAppContextSwitchesTests.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Reflection; using Xunit; @@ -10,10 +11,15 @@ namespace Microsoft.Data.SqlClient.Tests public class LocalAppContextSwitchesTests { [Theory] - [InlineData("SuppressInsecureTLSWarning", false)] [InlineData("LegacyRowVersionNullBehavior", false)] + [InlineData("SuppressInsecureTlsWarning", false)] [InlineData("MakeReadAsyncBlocking", false)] [InlineData("UseMinimumLoginTimeout", true)] + [InlineData("LegacyVarTimeZeroScaleBehaviour", true)] + [InlineData("UseConnectionPoolV2", false)] + #if NETFRAMEWORK + [InlineData("DisableTnirByDefault", false)] + #endif public void DefaultSwitchValue(string property, bool expectedDefaultValue) { var switchesType = typeof(SqlCommand).Assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches"); diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj similarity index 79% rename from src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj rename to src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj index 40f04d773d..ad2b1191a0 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.FunctionalTests.csproj @@ -12,7 +12,6 @@ - @@ -73,40 +72,45 @@ - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - + + + + + + + - + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + Common + Address @@ -119,11 +123,12 @@ TDS + - + @@ -131,9 +136,6 @@ %(Filename)%(Extension) - - - PreserveNewest diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlClientFactoryTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlClientFactoryTest.cs index b88ae0a260..a4b6cb1579 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlClientFactoryTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlClientFactoryTest.cs @@ -31,7 +31,12 @@ public void InstanceTest() }; [Theory] - [MemberData(nameof(FactoryMethodTestData))] + [MemberData( + nameof(FactoryMethodTestData), + // xUnit can't consistently serialize the data for this test, so we + // disable enumeration of the test data to avoid warnings on the + // console. + DisableDiscoveryEnumeration = true)] public void FactoryMethodTest(Func factory, Type expectedType, bool singleton) { object value1 = factory(); diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandSetTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandSetTest.cs index 1428bcc3e8..7e8a3f0377 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandSetTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandSetTest.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Data; using System.Reflection; using Xunit; @@ -29,9 +30,26 @@ public void AppendCommandWithEmptyString_Throws() VerifyException(ex, "CommandText property has not been initialized"); } + public static IEnumerable CommandTypeData() + { + return new object[][] + { + new object[] { CommandType.TableDirect }, + new object[] { (CommandType)5 } + }; + } + [Theory] - [InlineData(CommandType.TableDirect)] - [InlineData((CommandType)5)] + [MemberData( + nameof(CommandTypeData) +#if NETFRAMEWORK + // .NET Framework puts system enums in something called the Global + // Assembly Cache (GAC), and xUnit refuses to serialize enums that + // live there. So for .NET Framework, we disable enumeration of the + // test data to avoid warnings on the console when running tests. + , DisableDiscoveryEnumeration = true +#endif + )] public void AppendBadCommandType_Throws(CommandType commandType) { var cmdSet = CreateInstance(); diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs index 6bbc44f6b1..616a8fec6f 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionBasicTests.cs @@ -7,11 +7,15 @@ using System.Data.Common; using System.Diagnostics; using System.Globalization; +using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Security; using System.Threading; using System.Threading.Tasks; +using Microsoft.SqlServer.TDS; +using Microsoft.SqlServer.TDS.FeatureExtAck; +using Microsoft.SqlServer.TDS.Login7; using Microsoft.SqlServer.TDS.PreLogin; using Microsoft.SqlServer.TDS.Servers; using Xunit; @@ -65,7 +69,7 @@ public async Task PreLoginEncryptionExcludedTest() [PlatformSpecific(TestPlatforms.Windows)] public async Task TransientFaultTestAsync(uint errorCode) { - using TransientFaultTDSServer server = TransientFaultTDSServer.StartTestServer(true, true, errorCode); + using TransientFaultTDSServer server = TransientFaultTDSServer.StartTestServer(true, false, errorCode); SqlConnectionStringBuilder builder = new() { DataSource = "localhost," + server.Port, @@ -85,7 +89,7 @@ public async Task TransientFaultTestAsync(uint errorCode) [PlatformSpecific(TestPlatforms.Windows)] public void TransientFaultTest(uint errorCode) { - using TransientFaultTDSServer server = TransientFaultTDSServer.StartTestServer(true, true, errorCode); + using TransientFaultTDSServer server = TransientFaultTDSServer.StartTestServer(true, false, errorCode); SqlConnectionStringBuilder builder = new() { DataSource = "localhost," + server.Port, @@ -112,7 +116,7 @@ public void TransientFaultTest(uint errorCode) [PlatformSpecific(TestPlatforms.Windows)] public void TransientFaultDisabledTestAsync(uint errorCode) { - using TransientFaultTDSServer server = TransientFaultTDSServer.StartTestServer(true, true, errorCode); + using TransientFaultTDSServer server = TransientFaultTDSServer.StartTestServer(true, false, errorCode); SqlConnectionStringBuilder builder = new() { DataSource = "localhost," + server.Port, @@ -134,7 +138,7 @@ public void TransientFaultDisabledTestAsync(uint errorCode) [PlatformSpecific(TestPlatforms.Windows)] public void TransientFaultDisabledTest(uint errorCode) { - using TransientFaultTDSServer server = TransientFaultTDSServer.StartTestServer(true, true, errorCode); + using TransientFaultTDSServer server = TransientFaultTDSServer.StartTestServer(true, false, errorCode); SqlConnectionStringBuilder builder = new() { DataSource = "localhost," + server.Port, @@ -492,5 +496,129 @@ public void ConnectionTestAccessTokenCallbackCombinations() }); } } + + [Theory] + [InlineData(9, 0, 2047)] // SQL Server 2005 + [InlineData(10, 0, 2531)] // SQL Server 2008 + [InlineData(10, 50, 2500)] // SQL Server 2008 R2 + [InlineData(11, 0, 3000)] // SQL Server 2012-2022 + public void ConnectionTestPermittedVersion(int major, int minor, int build) + { + Version simulatedServerVersion = new Version(major, minor, build); + using TestTdsServer server = TestTdsServer.StartTestServer(serverVersion: simulatedServerVersion); + using SqlConnection conn = new SqlConnection(server.ConnectionString); + + conn.Open(); + Assert.Equal(ConnectionState.Open, conn.State); + + Version returnedServerVersion = Version.Parse(conn.ServerVersion); + + Assert.Equal(simulatedServerVersion, returnedServerVersion); + } + + [Theory] + [InlineData(7, 0, 623)] // SQL Server 7.0 + [InlineData(8, 0, 194)] // SQL Server 2000 RTM + [InlineData(8, 0, 384)] // SQL Server 2000 SP1 + public void ConnectionTestDeniedVersion(int major, int minor, int build) + { + Version simulatedServerVersion = new Version(major, minor, build); + using TestTdsServer server = TestTdsServer.StartTestServer(serverVersion: simulatedServerVersion); + using SqlConnection conn = new SqlConnection(server.ConnectionString); + + Assert.Throws(() => conn.Open()); + } + + + + // Test to verify that the server and client negotiate + // the common feature extension version. + // MDS currently supports vector feature ext version 0x1. + [Theory] + [InlineData(true, 0x2, 0x1)] + [InlineData(false, 0x0, 0x0)] + [InlineData(true, 0x1, 0x1)] + [InlineData(true, 0xFF, 0x0)] + public void TestConnWithVectorFeatExtVersionNegotiation(bool expectedConnectionResult, byte serverVersion, byte expectedNegotiatedVersion) + { + // Start the test TDS server. + using var server = TestTdsServer.StartTestServer(); + server.ServerSupportedVectorFeatureExtVersion = serverVersion; + server.EnableVectorFeatureExt = serverVersion == 0xFF ? false : true; + + byte expectedLoginReqFeatureExtId = (byte)TDSFeatureID.VectorSupport; + byte expectedLoginReqFeatureExtVersion = 0x1; + byte actualLoginReqFeatureExtId = 0; + byte actualLoginReqFeatureExtVersion = 0; + byte actualFeatureExtAckId = 0; + byte actualFeatureExtAckVersion = 0; + bool loginValuesFound = false; + bool responseValuesFound = false; + + server.OnLogin7Validated = loginToken => + { + if (loginToken.FeatureExt != null) + { + var optionToken = loginToken.FeatureExt + .OfType() + .FirstOrDefault(token => token.FeatureID == TDSFeatureID.VectorSupport); + + if (optionToken != null) + { + actualLoginReqFeatureExtId = (byte)optionToken.FeatureID; + actualLoginReqFeatureExtVersion = optionToken.Data[0]; + loginValuesFound = true; + } + } + }; + + server.OnAuthenticationResponseCompleted = response => + { + var featureExtAckToken = response + .OfType() + .FirstOrDefault(); + + if (featureExtAckToken != null) + { + var featureExtensionOption = featureExtAckToken.Options + .OfType() + .FirstOrDefault(option => option.FeatureID == TDSFeatureID.VectorSupport); + + if (featureExtensionOption != null) + { + actualFeatureExtAckId = (byte)featureExtensionOption.FeatureID; + actualFeatureExtAckVersion = featureExtensionOption.FeatureAckData[0]; + responseValuesFound = true; + } + } + }; + + // Connect to the test TDS server. + using var connection = new SqlConnection(server.ConnectionString); + if (expectedConnectionResult) + { + connection.Open(); + // Verify that the expected value was sent in the LOGIN packet. + Assert.Equal(expectedLoginReqFeatureExtId, actualLoginReqFeatureExtId); + Assert.Equal(expectedLoginReqFeatureExtVersion, actualLoginReqFeatureExtVersion); + Assert.True(loginValuesFound, "Expected login values not found in the login packet."); + // Verify that the expected values were received in the TDS response. + if (server.EnableVectorFeatureExt) + { + Assert.Equal(expectedLoginReqFeatureExtId, actualFeatureExtAckId); + Assert.Equal(expectedNegotiatedVersion, actualFeatureExtAckVersion); + Assert.True(responseValuesFound, "Expected response values not found in the login response."); + } + else + { + Assert.Equal(0x0, actualFeatureExtAckId); + Assert.Equal(0x0, actualFeatureExtAckVersion); + } + } + else + { + Assert.Throws(() => connection.Open()); + } + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs index 44c3db6ca1..30e232e88c 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionStringBuilderTest.cs @@ -492,7 +492,7 @@ public void EncryptTryParseInvalidValuesReturnsFalse(string value) Assert.Null(result); } - #region SqlConnectionEncryptOptionCoverterTests + #region TypeConverter Tests [Fact] public void ConnectionStringFromJsonTests() { @@ -530,6 +530,81 @@ public void ConnectionStringFromJsonThrowsException(string value) ExecuteConnectionStringFromJsonThrowsException(value); } + [Fact] + public void SqlConnectionStringBuilderConverterRoundTrip() + { + const string SampleConnectionString = "Data Source=localhost;Initial Catalog=master;Integrated Security=True"; + + Type instanceDescriptorType = typeof(System.ComponentModel.Design.Serialization.InstanceDescriptor); + // Verify that we can use the SqlConnectionStringBuilder's TypeConverter to round-trip a SqlConnectionStringBuilder + // to an InstanceDescriptor, then back to another SqlConnectionStringBuilder with the same resultant connection string. + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(SampleConnectionString); + TypeConverter converter = TypeDescriptor.GetConverter(typeof(SqlConnectionStringBuilder)); + + Assert.NotNull(converter); + Assert.True(converter.CanConvertTo(instanceDescriptorType)); + + object resultantInstanceDescriptor = converter.ConvertTo(builder, instanceDescriptorType); + + Assert.IsType(instanceDescriptorType, resultantInstanceDescriptor); + + object resultantConnectionStringBuilder = (resultantInstanceDescriptor as System.ComponentModel.Design.Serialization.InstanceDescriptor).Invoke(); + + Assert.IsType(resultantConnectionStringBuilder); + + Assert.Equal(builder.ConnectionString, (resultantConnectionStringBuilder as SqlConnectionStringBuilder).ConnectionString); + } + + [Fact] + public void DataSourceConverterCanConvertFromTest() + { + PropertyDescriptorCollection csbDescriptor = TypeDescriptor.GetProperties(typeof(SqlConnectionStringBuilder)); + PropertyDescriptor dataSourceDescriptor = csbDescriptor.Find(nameof(SqlConnectionStringBuilder.DataSource), false); + TypeConverter converter = dataSourceDescriptor?.Converter; + + Assert.NotNull(dataSourceDescriptor); + Assert.NotNull(converter); + // Use the converter to determine if can convert from string data type + Assert.True(converter.CanConvertFrom(null, typeof(string)), "Expecting to convert from a string type."); + // Use the same converter to determine if can convert from int or bool data types + Assert.False(converter.CanConvertFrom(null, typeof(int)), "Not expecting to convert from integer type."); + Assert.False(converter.CanConvertFrom(null, typeof(bool)), "Not expecting to convert from boolean type."); + } + + [Fact] + public void FailoverPartnerConverterCanConvertFromTest() + { + PropertyDescriptorCollection csbDescriptor = TypeDescriptor.GetProperties(typeof(SqlConnectionStringBuilder)); + PropertyDescriptor failoverPartnerDescriptor = csbDescriptor.Find(nameof(SqlConnectionStringBuilder.FailoverPartner), false); + TypeConverter converter = failoverPartnerDescriptor?.Converter; + + Assert.NotNull(failoverPartnerDescriptor); + Assert.NotNull(converter); + // Use the converter to determine if can convert from string data type + Assert.True(converter.CanConvertFrom(null, typeof(string)), "Expecting to convert from a string type."); + // Use the same converter to determine if can convert from int or bool data types + Assert.False(converter.CanConvertFrom(null, typeof(int)), "Not expecting to convert from integer type."); + Assert.False(converter.CanConvertFrom(null, typeof(bool)), "Not expecting to convert from boolean type."); + } + +#if NETFRAMEWORK + [Fact] + public void NetworkLibraryConverterCanConvertFromTest() + { + PropertyDescriptorCollection csbDescriptor = TypeDescriptor.GetProperties(typeof(SqlConnectionStringBuilder)); + PropertyDescriptor networkLibraryDescriptor = csbDescriptor.Find(nameof(SqlConnectionStringBuilder.NetworkLibrary), false); + TypeConverter converter = networkLibraryDescriptor?.Converter; + + Assert.NotNull(networkLibraryDescriptor); + Assert.NotNull(converter); + // Use the converter to determine if can convert from string data type + Assert.True(converter.CanConvertFrom(null, typeof(string)), "Expecting to convert from a string type."); + // Use the same converter to determine if can convert from int or bool data types + Assert.False(converter.CanConvertFrom(null, typeof(int)), "Not expecting to convert from integer type."); + Assert.False(converter.CanConvertFrom(null, typeof(bool)), "Not expecting to convert from boolean type."); + } +#endif + [Fact] public void SqlConnectionEncryptOptionConverterCanConvertFromTest() { @@ -543,19 +618,96 @@ public void SqlConnectionEncryptOptionConverterCanConvertFromTest() Assert.False(converter.CanConvertFrom(null, typeof(bool)), "Not expecting to convert from boolean type."); } + [Fact] + public void DataSourceConverterCanConvertToTest() + { + PropertyDescriptorCollection csbDescriptor = TypeDescriptor.GetProperties(typeof(SqlConnectionStringBuilder)); + PropertyDescriptor dataSourceDescriptor = csbDescriptor.Find(nameof(SqlConnectionStringBuilder.DataSource), false); + TypeConverter converter = dataSourceDescriptor?.Converter; + + Assert.NotNull(dataSourceDescriptor); + Assert.NotNull(converter); + // Use the converter to check if can convert from string + Assert.True(converter.CanConvertTo(null, typeof(string)), "Expecting to convert to a string type."); + // Use the same convert to check if can convert to int or bool + Assert.False(converter.CanConvertTo(null, typeof(int)), "Not expecting to convert to integer type."); + Assert.False(converter.CanConvertTo(null, typeof(bool)), "Not expecting to convert to boolean type."); + } + + [Fact] + public void FailoverPartnerConverterCanConvertToTest() + { + PropertyDescriptorCollection csbDescriptor = TypeDescriptor.GetProperties(typeof(SqlConnectionStringBuilder)); + PropertyDescriptor failoverPartnerDescriptor = csbDescriptor.Find(nameof(SqlConnectionStringBuilder.FailoverPartner), false); + TypeConverter converter = failoverPartnerDescriptor?.Converter; + + Assert.NotNull(failoverPartnerDescriptor); + Assert.NotNull(converter); + // Use the converter to check if can convert from string + Assert.True(converter.CanConvertTo(null, typeof(string)), "Expecting to convert to a string type."); + // Use the same convert to check if can convert to int or bool + Assert.False(converter.CanConvertTo(null, typeof(int)), "Not expecting to convert to integer type."); + Assert.False(converter.CanConvertTo(null, typeof(bool)), "Not expecting to convert to boolean type."); + } + +#if NETFRAMEWORK + [Fact] + public void NetworkLibraryConverterCanConvertToTest() + { + PropertyDescriptorCollection csbDescriptor = TypeDescriptor.GetProperties(typeof(SqlConnectionStringBuilder)); + PropertyDescriptor networkLibraryDescriptor = csbDescriptor.Find(nameof(SqlConnectionStringBuilder.NetworkLibrary), false); + TypeConverter converter = networkLibraryDescriptor?.Converter; + + Assert.NotNull(networkLibraryDescriptor); + Assert.NotNull(converter); + // Use the converter to check if can convert from string + Assert.True(converter.CanConvertTo(null, typeof(string)), "Expecting to convert to a string type."); + // Use the same convert to check if can convert to int or bool + Assert.False(converter.CanConvertTo(null, typeof(int)), "Not expecting to convert to integer type."); + Assert.False(converter.CanConvertTo(null, typeof(bool)), "Not expecting to convert to boolean type."); + } +#endif + [Fact] public void SqlConnectionEncryptOptionConverterCanConvertToTest() { // Get a converter SqlConnectionEncryptOption option = SqlConnectionEncryptOption.Parse("false"); TypeConverter converter = TypeDescriptor.GetConverter(option.GetType()); - // Use the converter to check if can convert from stirng + // Use the converter to check if can convert from string Assert.True(converter.CanConvertTo(null, typeof(string)), "Expecting to convert to a string type."); // Use the same convert to check if can convert to int or bool - Assert.False(converter.CanConvertTo(null, typeof(int)), "Not expecting to convert from integer type."); - Assert.False(converter.CanConvertTo(null, typeof(bool)), "Not expecting to convert from boolean type."); + Assert.False(converter.CanConvertTo(null, typeof(int)), "Not expecting to convert to integer type."); + Assert.False(converter.CanConvertTo(null, typeof(bool)), "Not expecting to convert to boolean type."); } +#if NETFRAMEWORK + [Fact] + public void NetworkLibraryGetStandardValuesTest() + { + PropertyDescriptorCollection csbDescriptor = TypeDescriptor.GetProperties(typeof(SqlConnectionStringBuilder)); + PropertyDescriptor networkLibraryDescriptor = csbDescriptor.Find(nameof(SqlConnectionStringBuilder.NetworkLibrary), false); + TypeConverter converter = networkLibraryDescriptor?.Converter; + + Assert.NotNull(networkLibraryDescriptor); + Assert.NotNull(converter); + + Assert.True(converter.GetStandardValuesSupported()); + Assert.False(converter.GetStandardValuesExclusive()); + + System.Collections.ICollection networkLibraries = converter.GetStandardValues(); + string[] networkLibraryArray = new string[4]; + + Assert.Equal(4, networkLibraries.Count); + networkLibraries.CopyTo(networkLibraryArray, 0); + + Assert.NotEqual(-1, Array.IndexOf(networkLibraryArray, "Named Pipes (DBNMPNTW)")); + Assert.NotEqual(-1, Array.IndexOf(networkLibraryArray, "Shared Memory (DBMSLPCN)")); + Assert.NotEqual(-1, Array.IndexOf(networkLibraryArray, "TCP/IP (DBMSSOCN)")); + Assert.NotEqual(-1, Array.IndexOf(networkLibraryArray, "VIA (DBMSGNET)")); + } +#endif + [Fact] public void SqlConnectionEncryptOptionConverterConvertFromTest() { @@ -577,6 +729,63 @@ public void SqlConnectionEncryptOptionConverterConvertFromTest() Assert.Throws(() => converter.ConvertFrom(true)); } +#if NETFRAMEWORK + [Theory] + [InlineData("Named Pipes (DBNMPNTW)", "dbnmpntw")] + [InlineData("Shared Memory (DBMSLPCN)", "dbmslpcn")] + [InlineData("TCP/IP (DBMSSOCN)", "dbmssocn")] + [InlineData("VIA (DBMSGNET)", "dbmsgnet")] + public void NetworkLibraryConvertFromValidValueTest(string inputValue, string expectedOutput) + { + PropertyDescriptorCollection csbDescriptor = TypeDescriptor.GetProperties(typeof(SqlConnectionStringBuilder)); + PropertyDescriptor networkLibraryDescriptor = csbDescriptor.Find(nameof(SqlConnectionStringBuilder.NetworkLibrary), false); + TypeConverter converter = networkLibraryDescriptor?.Converter; + + Assert.NotNull(networkLibraryDescriptor); + Assert.NotNull(converter); + + // Test case 1: input value as-is + object outputValue = converter.ConvertFrom(inputValue); + + Assert.IsType(outputValue); + Assert.Equal(expectedOutput, outputValue as string); + + // Test case 2: input value with leading/trailing spaces + outputValue = converter.ConvertFrom(" " + inputValue + " "); + Assert.IsType(outputValue); + Assert.Equal(expectedOutput, outputValue as string); + + // Test case 3: input value with mixed case + outputValue = converter.ConvertFrom(inputValue.ToUpper()); + Assert.IsType(outputValue); + Assert.Equal(expectedOutput, outputValue as string); + } + + [Fact] + public void NetworkLibraryConvertFromInvalidValueTest() + { + const string InvalidValue = "Sample invalid value"; + + PropertyDescriptorCollection csbDescriptor = TypeDescriptor.GetProperties(typeof(SqlConnectionStringBuilder)); + PropertyDescriptor networkLibraryDescriptor = csbDescriptor.Find(nameof(SqlConnectionStringBuilder.NetworkLibrary), false); + TypeConverter converter = networkLibraryDescriptor?.Converter; + + Assert.NotNull(networkLibraryDescriptor); + Assert.NotNull(converter); + + // Test case 1: input value as-is + object outputValue = converter.ConvertFrom(InvalidValue); + + Assert.IsType(outputValue); + Assert.Equal(InvalidValue, outputValue as string); + + // Test case 2: input value with leading/trailing spaces + outputValue = converter.ConvertFrom(" " + InvalidValue + " "); + Assert.IsType(outputValue); + Assert.Equal(InvalidValue, outputValue as string); + } +#endif + [Fact] public void SqlConnectionEncryptOptionConverterConvertToTest() { @@ -596,6 +805,61 @@ public void SqlConnectionEncryptOptionConverterConvertToTest() Assert.Throws(() => converter.ConvertTo(SqlConnectionEncryptOption.Parse("false"), typeof(bool))); } +#if NETFRAMEWORK + [Theory] + [InlineData("dbnmpntw", "Named Pipes (DBNMPNTW)")] + [InlineData("dbmslpcn", "Shared Memory (DBMSLPCN)")] + [InlineData("dbmssocn", "TCP/IP (DBMSSOCN)")] + [InlineData("dbmsgnet", "VIA (DBMSGNET)")] + public void NetworkLibraryConvertToValidValueTest(string inputValue, string expectedOutput) + { + PropertyDescriptorCollection csbDescriptor = TypeDescriptor.GetProperties(typeof(SqlConnectionStringBuilder)); + PropertyDescriptor networkLibraryDescriptor = csbDescriptor.Find(nameof(SqlConnectionStringBuilder.NetworkLibrary), false); + TypeConverter converter = networkLibraryDescriptor?.Converter; + + Assert.NotNull(networkLibraryDescriptor); + Assert.NotNull(converter); + + // Test case 1: input value as-is + object outputValue = converter.ConvertTo(inputValue, typeof(string)); + + Assert.IsType(outputValue); + Assert.Equal(expectedOutput, outputValue as string); + + // Test case 2: input value with leading/trailing spaces + outputValue = converter.ConvertTo(" " + inputValue + " ", typeof(string)); + Assert.IsType(outputValue); + Assert.Equal(expectedOutput, outputValue as string); + + // Test case 3: input value with mixed case + outputValue = converter.ConvertTo(inputValue.ToUpper(), typeof(string)); + Assert.IsType(outputValue); + Assert.Equal(expectedOutput, outputValue as string); + } + + [Fact] + public void NetworkLibraryConvertToInvalidValueTest() + { + const string InvalidValue = " Sample invalid value "; + + PropertyDescriptorCollection csbDescriptor = TypeDescriptor.GetProperties(typeof(SqlConnectionStringBuilder)); + PropertyDescriptor networkLibraryDescriptor = csbDescriptor.Find(nameof(SqlConnectionStringBuilder.NetworkLibrary), false); + TypeConverter converter = networkLibraryDescriptor?.Converter; + + Assert.NotNull(networkLibraryDescriptor); + Assert.NotNull(converter); + + // In all cases, the output value should be the same as the input value + object outputValue = converter.ConvertTo(InvalidValue, typeof(string)); + + Assert.IsType(outputValue); + Assert.Equal(InvalidValue, outputValue as string); + + // Converting to an invalid type should throw an exception + Assert.Throws(() => converter.ConvertTo(InvalidValue, typeof(int))); + } +#endif + internal class UserDbConnectionStringSettings { [Required] @@ -643,7 +907,7 @@ private static TSettings LoadSettingsFromJsonStream(string encryptOpt return settingsOut; } - #endregion +#endregion internal void ExecuteConnectionStringTests(string connectionString) { diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs index 54b04234d6..d61866f176 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlConnectionTest.cs @@ -1082,10 +1082,30 @@ public void ConnectionRetryForNonAzureEndpoints() Assert.Equal(1, (int)field.GetValue(cn)); } + + [Fact] - public void ConnectionRetryForAzureDbEndpoints() + public void ConnectionString_WithOnlyComma() { - SqlConnection cn = new SqlConnection("Data Source = someserver.database.windows.net"); + // Test Case for https://github.com/dotnet/SqlClient/issues/3110 + // Validates that a single-comma Data Source (e.g., "Data Source=,") no longer causes ArgumentOutOfRangeException + // Instead, it should throw a SqlException indicating a connection failure + + SqlConnection cn = new SqlConnection("Data Source=,;Initial Catalog=master;Integrated Security=True"); + Assert.Throws(() => { cn.Open(); }); + + } + + [Theory] + [InlineData("myserver.database.windows.net")] + [InlineData("myserver.database.cloudapi.de")] + [InlineData("myserver.database.usgovcloudapi.net")] + [InlineData("myserver.DATABASE.usgovcloudapi.net")] + [InlineData("myserver.database.chinacloudapi.cn")] + [InlineData("myserver.database.fabric.microsoft.com")] + public void ConnectionRetryForAzureDbEndpoints(string serverName) + { + SqlConnection cn = new SqlConnection($"Data Source = {serverName}"); FieldInfo field = typeof(SqlConnection).GetField("_connectRetryCount", BindingFlags.Instance | BindingFlags.NonPublic); Assert.NotNull(field.GetValue(cn)); Assert.Equal(2, (int)field.GetValue(cn)); @@ -1093,7 +1113,13 @@ public void ConnectionRetryForAzureDbEndpoints() [Theory] [InlineData("myserver-ondemand.sql.azuresynapse.net")] + [InlineData("myserver-ondemand.SQL.azuresynapse.net")] [InlineData("someserver-ondemand.database.windows.net")] + [InlineData("datawarehouse.fabric.microsoft.com")] + [InlineData("datawarehouse.FABRIC.microsoft.com")] + [InlineData("datawarehouse.pbidedicated.microsoft.com")] + [InlineData("someserver.pbidedicated.microsoft.com")] + [InlineData("someserver.pbidedicated.windows.net")] public void ConnectionRetryForAzureOnDemandEndpoints(string serverName) { SqlConnection cn = new SqlConnection($"Data Source = {serverName}"); diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDataRecordTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDataRecordTest.cs index 0e748c5d74..0576a64eba 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDataRecordTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlDataRecordTest.cs @@ -34,7 +34,11 @@ public void SqlRecordFillTest() new SqlMetaData("col11", SqlDbType.Real), new SqlMetaData("col12", SqlDbType.Decimal), new SqlMetaData("col13", SqlDbType.Money), - new SqlMetaData("col14", SqlDbType.Variant) + new SqlMetaData("col14", SqlDbType.Variant), +#if NET + new SqlMetaData("col15", SqlDbType.Date), + new SqlMetaData("col16", SqlDbType.Time), +#endif }; SqlDataRecord record = new SqlDataRecord(metaData); @@ -116,13 +120,22 @@ public void SqlRecordFillTest() record.SetSqlMoney(12, SqlMoney.MaxValue); Assert.Equal(SqlMoney.MaxValue, record.GetSqlMoney(12)); + int offset = 1; +#if NET + offset = 3; + record.SetValue(14, new DateOnly(2025, 11,28)); + Assert.Equal(new DateTime(2025, 11, 28), record.GetValue(14)); + + record.SetValue(15, new TimeOnly(1, 57, 58)); + Assert.Equal(new TimeSpan(1, 57, 58), record.GetValue(15)); +#endif // Try adding different values to SqlVariant type - for (int i = 0; i < record.FieldCount - 1; ++i) + for (int i = 0; i < record.FieldCount - offset; ++i) { object valueToSet = record.GetSqlValue(i); - record.SetValue(record.FieldCount - 1, valueToSet); - object o = record.GetSqlValue(record.FieldCount - 1); + record.SetValue(record.FieldCount - offset, valueToSet); + object o = record.GetSqlValue(record.FieldCount - offset); if (o is SqlBinary) { @@ -133,8 +146,8 @@ public void SqlRecordFillTest() Assert.Equal(valueToSet, o); } - record.SetDBNull(record.FieldCount - 1); - Assert.Equal(DBNull.Value, record.GetSqlValue(record.FieldCount - 1)); + record.SetDBNull(record.FieldCount - offset); + Assert.Equal(DBNull.Value, record.GetSqlValue(record.FieldCount - offset)); record.SetDBNull(i); Assert.Equal(DBNull.Value, record.GetValue(i)); @@ -320,7 +333,13 @@ public void GetChar_ThrowsNotSupported() } [Theory] - [ClassData(typeof(GetUdtTypeTestData))] + [MemberData( + nameof(GetUdtTypeTestData.Get), + MemberType = typeof(GetUdtTypeTestData), + // xUnit can't consistently serialize the data for this test, so we + // disable enumeration of the test data to avoid warnings on the + // console. + DisableDiscoveryEnumeration = true)] public void GetUdt_ReturnsValue(Type udtType, object value, string serverTypeName) { SqlMetaData[] metadata = new SqlMetaData[] { new SqlMetaData(nameof(udtType.Name), SqlDbType.Udt, udtType, serverTypeName) }; @@ -333,7 +352,10 @@ public void GetUdt_ReturnsValue(Type udtType, object value, string serverTypeNam } [Theory] - [ClassData(typeof(GetXXXBadTypeTestData))] + [MemberData( + nameof(GetXXXBadTypeTestData.Get), + MemberType = typeof(GetXXXBadTypeTestData), + DisableDiscoveryEnumeration = true)] public void GetXXX_ThrowsIfBadType(Func getXXX) { SqlMetaData[] metaData = new SqlMetaData[] @@ -347,7 +369,10 @@ public void GetXXX_ThrowsIfBadType(Func getXXX) } [Theory] - [ClassData(typeof(GetXXXCheckValueTestData))] + [MemberData( + nameof(GetXXXCheckValueTestData.Get), + MemberType = typeof(GetXXXCheckValueTestData), + DisableDiscoveryEnumeration = true)] public void GetXXX_ReturnValue(SqlDbType dbType, object value, Func getXXX) { SqlMetaData[] metaData = new SqlMetaData[] @@ -361,9 +386,9 @@ public void GetXXX_ReturnValue(SqlDbType dbType, object value, Func + public class GetXXXBadTypeTestData { - public IEnumerator GetEnumerator() + public static IEnumerable Get() { yield return new object[] { new Func(r => r.GetGuid(0)) }; yield return new object[] { new Func(r => r.GetInt16(0)) }; @@ -376,31 +401,21 @@ public IEnumerator GetEnumerator() yield return new object[] { new Func(r => r.GetDateTimeOffset(0)) }; yield return new object[] { new Func(r => r.GetTimeSpan(0)) }; } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } } - public class GetUdtTypeTestData : IEnumerable + public class GetUdtTypeTestData { - public IEnumerator GetEnumerator() + public static IEnumerable Get() { yield return new object[] { typeof(SqlGeography), SqlGeography.Point(43, -81, 4326), "Geography" }; yield return new object[] { typeof(SqlGeometry), SqlGeometry.Point(43, -81, 4326), "Geometry" }; yield return new object[] { typeof(SqlHierarchyId), SqlHierarchyId.Parse("/"), "HierarchyId" }; } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } } - public class GetXXXCheckValueTestData : IEnumerable + public class GetXXXCheckValueTestData { - public IEnumerator GetEnumerator() + public static IEnumerable Get() { yield return new object[] { SqlDbType.UniqueIdentifier, Guid.NewGuid(), new Func(r => r.GetGuid(0)) }; yield return new object[] { SqlDbType.SmallInt, (short)123, new Func(r => r.GetInt16(0)) }; @@ -417,11 +432,6 @@ public IEnumerator GetEnumerator() yield return new object[] { SqlDbType.SmallDateTime, DateTime.Now, new Func(r => r.GetDateTime(0)) }; yield return new object[] { SqlDbType.TinyInt, (byte)1, new Func(r => r.GetByte(0)) }; } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } } [SqlServer.Server.SqlUserDefinedType(SqlServer.Server.Format.UserDefined)] public class TestUdt {} diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlExceptionTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlExceptionTest.cs index 85d3a02fb1..5106a287c5 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlExceptionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlExceptionTest.cs @@ -15,52 +15,24 @@ public class SqlExceptionTest private const string badServer = "92B96911A0BD43E8ADA4451031F7E7CF"; [Fact] - [ActiveIssue("12161", TestPlatforms.AnyUnix)] public void SerializationTest() { SqlException e = CreateException(); string json = JsonConvert.SerializeObject(e); - var settings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All, - }; - - // TODO: Deserialization fails on Unix with "Member 'ClassName' was not found." + var settings = new JsonSerializerSettings(); var sqlEx = JsonConvert.DeserializeObject(json, settings); Assert.Equal(e.ClientConnectionId, sqlEx.ClientConnectionId); Assert.Equal(e.StackTrace, sqlEx.StackTrace); } -#if NETFRAMEWORK - [Fact] - [ActiveIssue("12161", TestPlatforms.AnyUnix)] - public static void SqlExcpetionSerializationTest() - { - var formatter = new BinaryFormatter(); - SqlException e = CreateException(); - using (var stream = new MemoryStream()) - { - try - { - formatter.Serialize(stream, e); - stream.Position = 0; - var e2 = (SqlException)formatter.Deserialize(stream); - } - catch (Exception ex) - { - Assert.Fail($"Unexpected Exception occurred: {ex.Message}"); - } - } - } -#endif - [Fact] public void JSONSerializationTest() { string clientConnectionId = "90cdab4d-2145-4c24-a354-c8ccff903542"; - string json = @"{""ClassName"":""Microsoft.Data.SqlClient.SqlException""," + string json = @"{" + + @"""ClassName"":""Microsoft.Data.SqlClient.SqlException""," + @"""Message"":""A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 40 - Could not open a connection to SQL Server)""," + @"""Data"":{""HelpLink.ProdName"":""Microsoft SQL Server""," + @"""HelpLink.EvtSrc"":""MSSQLServer""," @@ -79,13 +51,10 @@ public void JSONSerializationTest() + @"""Source"":""Core .Net SqlClient Data Provider""," + @"""WatsonBuckets"":null," + @"""Errors"":null," - + @"""ClientConnectionId"":""90cdab4d-2145-4c24-a354-c8ccff903542""}"; - - var settings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All, - }; + + @"""ClientConnectionId"":""90cdab4d-2145-4c24-a354-c8ccff903542""" + + @"}"; + var settings = new JsonSerializerSettings(); var sqlEx = JsonConvert.DeserializeObject(json, settings); Assert.IsType(sqlEx); Assert.Equal(clientConnectionId, sqlEx.ClientConnectionId.ToString()); diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlMetaDataTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlMetaDataTest.cs index dd6c228fd2..cd316a9eab 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlMetaDataTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlMetaDataTest.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Data; using System.Data.SqlTypes; using System.Globalization; @@ -15,7 +16,12 @@ namespace Microsoft.Data.SqlClient.Tests public class SqlMetaDataTest { [Theory] - [MemberData(nameof(SqlMetaDataAdjustValues))] + [MemberData( + nameof(SqlMetaDataAdjustValues), + // xUnit can't consistently serialize the data for this test, so we + // disable enumeration of the test data to avoid warnings on the + // console. + DisableDiscoveryEnumeration = true)] [MemberData(nameof(SqlMetaDataDateTimeValues))] public void Adjust(SqlDbType dbType, object expected) { @@ -37,7 +43,9 @@ public void Adjust(SqlDbType dbType, object expected) } [Theory] - [MemberData(nameof(SqlMetaDataMaxLengthTrimValues))] + [MemberData( + nameof(SqlMetaDataMaxLengthTrimValues), + DisableDiscoveryEnumeration = true)] public void AdjustWithGreaterThanMaxLengthValues(SqlDbType dbType, object value) { int maxLength = 4; @@ -59,7 +67,9 @@ public void AdjustWithGreaterThanMaxLengthValues(SqlDbType dbType, object value) } [Theory] - [MemberData(nameof(SqlMetaDataInvalidValues))] + [MemberData( + nameof(SqlMetaDataInvalidValues), + DisableDiscoveryEnumeration = true)] public void AdjustWithInvalidType_Throws(SqlDbType dbType, object expected) { SqlMetaData metaData = new SqlMetaData( @@ -229,11 +239,28 @@ public void ConstructorWithDefaultLocaleInvalidType_Throws() Assert.Contains("dbType", ex.Message, StringComparison.OrdinalIgnoreCase); } + public static IEnumerable ConstructorCharData() + { + return new object[][] + { + new object[] { SqlDbType.Char }, + new object[] { SqlDbType.VarChar }, + new object[] { SqlDbType.NChar }, + new object[] { SqlDbType.NVarChar } + }; + } + [Theory] - [InlineData(SqlDbType.Char)] - [InlineData(SqlDbType.VarChar)] - [InlineData(SqlDbType.NChar)] - [InlineData(SqlDbType.NVarChar)] + [MemberData( + nameof(ConstructorCharData) +#if NETFRAMEWORK + // .NET Framework puts system enums in something called the Global + // Assembly Cache (GAC), and xUnit refuses to serialize enums that + // live there. So for .NET Framework, we disable enumeration of the + // test data to avoid warnings on the console when running tests. + , DisableDiscoveryEnumeration = true +#endif + )] public void ConstructorWithMaxLengthAndDefaultLocale(SqlDbType dbType) { const int maxLength = 5; @@ -257,10 +284,12 @@ public void ConstructorWithMaxLengthAndDefaultLocaleInvalidType_Throws() } [Theory] - [InlineData(SqlDbType.Char)] - [InlineData(SqlDbType.VarChar)] - [InlineData(SqlDbType.NChar)] - [InlineData(SqlDbType.NVarChar)] + [MemberData( + nameof(ConstructorCharData) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void ConstructorWithMaxLengthAndLocale(SqlDbType dbType) { long maxLength = 5L; @@ -286,9 +315,22 @@ public void ConstructorWithMaxLengthAndLocaleInvalidType_Throws() Assert.Contains("dbType", ex.Message, StringComparison.OrdinalIgnoreCase); } + public static IEnumerable ConstructorTextData() + { + return new object[][] + { + new object[] { SqlDbType.NText }, + new object[] { SqlDbType.Text } + }; + } + [Theory] - [InlineData(SqlDbType.NText)] - [InlineData(SqlDbType.Text)] + [MemberData( + nameof(ConstructorTextData) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void ConstructorWithMaxLengthTextAndDefaultLocale(SqlDbType dbType) { long maxLength = SqlMetaData.Max; @@ -302,8 +344,12 @@ public void ConstructorWithMaxLengthTextAndDefaultLocale(SqlDbType dbType) } [Theory] - [InlineData(SqlDbType.NText)] - [InlineData(SqlDbType.Text)] + [MemberData( + nameof(ConstructorTextData) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void ConstructorWithMaxLengthTextAndLocale(SqlDbType dbType) { long maxLength = SqlMetaData.Max; @@ -319,11 +365,18 @@ public void ConstructorWithMaxLengthTextAndLocale(SqlDbType dbType) } [Theory] - [InlineData(SqlDbType.Char)] - [InlineData(SqlDbType.VarChar)] - [InlineData(SqlDbType.NChar)] - [InlineData(SqlDbType.NVarChar)] - [InlineData(SqlDbType.NText)] + [MemberData( + nameof(ConstructorCharData) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] + [MemberData( + nameof(ConstructorTextData) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void ConstructorWithInvalidMaxLengthAndLocale_Throws(SqlDbType dbType) { int invalidMaxLength = -2; @@ -348,15 +401,35 @@ public void ConstructorWithInvalidMaxLengthAndLocaleCompareOptionsBinarySortAndI Assert.Contains("invalid", ex.Message, StringComparison.OrdinalIgnoreCase); } + public static IEnumerable ConstructorBinaryData() + { + return new object[][] + { + new object[] { SqlDbType.Binary }, + new object[] { SqlDbType.VarBinary }, + new object[] { SqlDbType.Image } + }; + } + [Theory] - [InlineData(SqlDbType.Char)] - [InlineData(SqlDbType.VarChar)] - [InlineData(SqlDbType.NChar)] - [InlineData(SqlDbType.NVarChar)] - [InlineData(SqlDbType.NText)] - [InlineData(SqlDbType.Binary)] - [InlineData(SqlDbType.VarBinary)] - [InlineData(SqlDbType.Image)] + [MemberData( + nameof(ConstructorCharData) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] + [MemberData( + nameof(ConstructorTextData) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] + [MemberData( + nameof(ConstructorBinaryData) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void ConstructorWithInvalidMaxLengthDefaultLocale_Throws(SqlDbType dbType) { int invalidMaxLength = -2; @@ -506,9 +579,22 @@ public void DecimalConstructorWithPrecisionOutofRange2_Throws() // Assert.Contains("scale", ex.Message, StringComparison.OrdinalIgnoreCase); //} + public static IEnumerable ConstructorGenericData() + { + return new object[][] + { + new object[] { SqlDbType.Variant, null }, + new object[] { SqlDbType.Udt, typeof(Address) } + }; + } + [Theory] - [InlineData(SqlDbType.Variant, null)] - [InlineData(SqlDbType.Udt, typeof(Address))] + [MemberData( + nameof(ConstructorGenericData) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void GenericConstructorWithoutXmlSchema(SqlDbType dbType, Type udt) { if (udt != null) @@ -534,50 +620,10 @@ public void GenericConstructorWithoutXmlSchemaWithInvalidDbType_Throws() Assert.Contains("dbType", ex.Message, StringComparison.OrdinalIgnoreCase); } - [Fact] - public void GetPartialLengthWithXmlSqlMetaDataType_Throws() - { - Type sqlMetaDataType = typeof(SqlMetaData); - SqlMetaData exampleMetaData = new SqlMetaData("col2", SqlDbType.Xml); - MethodInfo method = sqlMetaDataType.GetMethod("GetPartialLengthMetaData", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); - Exception ex = Assert.ThrowsAny(() => - { - SqlMetaData metaData = (SqlMetaData)method.Invoke(exampleMetaData, new object[] { exampleMetaData }); - }); - Assert.NotNull(ex.InnerException); - Assert.IsType(ex.InnerException); - Assert.NotEmpty(ex.InnerException.Message); - Assert.Contains("metadata", ex.InnerException.Message, StringComparison.OrdinalIgnoreCase); - } - - [Theory] - [InlineData(SqlDbType.NVarChar)] - [InlineData(SqlDbType.VarChar)] - [InlineData(SqlDbType.VarBinary)] - public void GetPartialLengthWithVarSqlMetaDataType(SqlDbType sqlDbType) - { - Type sqlMetaDataType = typeof(SqlMetaData); - SqlMetaData exampleMetaData = new SqlMetaData("col2", sqlDbType, 16); - MethodInfo method = sqlMetaDataType.GetMethod("GetPartialLengthMetaData", BindingFlags.NonPublic | BindingFlags.Static); - SqlMetaData metaData = metaData = (SqlMetaData)method.Invoke(exampleMetaData, new object[] { exampleMetaData }); - Assert.Equal(exampleMetaData.Name, metaData.Name); - Assert.Equal(exampleMetaData.SqlDbType, metaData.SqlDbType); - Assert.Equal(SqlMetaData.Max, metaData.MaxLength); - Assert.Equal(0, metaData.Precision); - Assert.Equal(0, metaData.Scale); - Assert.Equal(exampleMetaData.LocaleId, metaData.LocaleId); - Assert.Equal(exampleMetaData.CompareOptions, metaData.CompareOptions); - Assert.Null(metaData.XmlSchemaCollectionDatabase); - Assert.Null(metaData.XmlSchemaCollectionName); - Assert.Null(metaData.XmlSchemaCollectionOwningSchema); - // PartialLength is an interal property which is why reflection is required. - PropertyInfo isPartialLengthProp = sqlMetaDataType.GetProperty("IsPartialLength", BindingFlags.NonPublic | BindingFlags.Instance); - Assert.True((bool)isPartialLengthProp.GetValue(metaData)); - Assert.Equal(exampleMetaData.Type, metaData.Type); - } - [Theory] - [MemberData(nameof(SqlMetaDataInferredValues))] + [MemberData( + nameof(SqlMetaDataInferredValues), + DisableDiscoveryEnumeration = true)] public void InferFromValue(SqlDbType expectedDbType, object value) { SqlMetaData metaData = SqlMetaData.InferFromValue(value, "col1"); @@ -621,18 +667,6 @@ public void InferFromValueWithUdtValue_Throws() Assert.Contains("address", ex.Message, StringComparison.OrdinalIgnoreCase); } - [Fact] - public void IsPartialLengthTrueGetPartialLengthMetaData() - { - Type sqlMetaDataType = typeof(SqlMetaData); - SqlMetaData exampleMetaData = new SqlMetaData("col2", SqlDbType.Int); - FieldInfo isPartialLengthField = sqlMetaDataType.GetField("_partialLength", BindingFlags.NonPublic | BindingFlags.Instance); - isPartialLengthField.SetValue(exampleMetaData, true); - MethodInfo method = sqlMetaDataType.GetMethod("GetPartialLengthMetaData", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); - SqlMetaData metaData = (SqlMetaData)method.Invoke(exampleMetaData, new object[] { exampleMetaData }); - Assert.Equal(exampleMetaData, metaData); - } - [Fact] public void NameDbTypeDatabaseOwningSchemaObjectNameConstructor() { @@ -645,16 +679,6 @@ public void NameDbTypeDatabaseOwningSchemaObjectNameConstructor() Assert.Equal("xml", metaData.TypeName); } - [Fact] - public void NonVarTypeGetPartialLengthMetaData() - { - Type sqlMetaDataType = typeof(SqlMetaData); - SqlMetaData exampleMetaData = new SqlMetaData("col2", SqlDbType.Int); - MethodInfo method = sqlMetaDataType.GetMethod("GetPartialLengthMetaData", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); - SqlMetaData metaData = (SqlMetaData)method.Invoke(exampleMetaData, new object[] { exampleMetaData }); - Assert.Equal(exampleMetaData, metaData); - } - [Fact] public void StringConstructorWithLocaleCompareOption() { @@ -672,10 +696,23 @@ public void StringConstructorWithLocaleCompareOption() Assert.Equal(1, metaData.SortOrdinal); } + public static IEnumerable ConstructorTimeData() + { + return new object[][] + { + new object[] { SqlDbType.Time }, + new object[] { SqlDbType.DateTime2 }, + new object[] { SqlDbType.DateTimeOffset } + }; + } + [Theory] - [InlineData(SqlDbType.Time)] - [InlineData(SqlDbType.DateTime2)] - [InlineData(SqlDbType.DateTimeOffset)] + [MemberData( + nameof(ConstructorTimeData) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void TimeConstructorWithOutOfRange_Throws(SqlDbType dbType) { byte precision = 8; diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs index ad6b7ec259..f62c5a70c8 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs @@ -3,12 +3,15 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.SqlTypes; using System.Reflection; using Xunit; +using SwitchesHelper = Microsoft.Data.SqlClient.Tests.Common.LocalAppContextSwitchesHelper; + namespace Microsoft.Data.SqlClient.Tests { public class SqlParameterTests @@ -217,37 +220,54 @@ public void Constructor2_Name_Null() Assert.Equal(string.Empty, p.ParameterName); } + public static IEnumerable Constructor3Data() + { + return new object[][] + { + new object[]{ SqlDbType.BigInt, DbType.Int64 }, + new object[]{ SqlDbType.Binary, DbType.Binary }, + new object[]{ SqlDbType.Bit, DbType.Boolean }, + new object[]{ SqlDbType.Char, DbType.AnsiStringFixedLength }, + new object[]{ SqlDbType.Date, DbType.Date }, + new object[]{ SqlDbType.DateTime, DbType.DateTime }, + new object[]{ SqlDbType.DateTimeOffset, DbType.DateTimeOffset }, + new object[]{ SqlDbType.Decimal, DbType.Decimal }, + new object[]{ SqlDbType.Float, DbType.Double }, + new object[]{ SqlDbType.Image, DbType.Binary }, + new object[]{ SqlDbType.Int, DbType.Int32 }, + new object[]{ SqlDbType.Money, DbType.Currency }, + new object[]{ SqlDbType.NChar, DbType.StringFixedLength }, + new object[]{ SqlDbType.NText, DbType.String }, + new object[]{ SqlDbType.NVarChar, DbType.String }, + new object[]{ SqlDbType.Real, DbType.Single }, + new object[]{ SqlDbType.SmallDateTime, DbType.DateTime }, + new object[]{ SqlDbType.SmallInt, DbType.Int16 }, + new object[]{ SqlDbType.SmallMoney, DbType.Currency }, + new object[]{ SqlDbType.Structured, DbType.Object }, + new object[]{ SqlDbType.Text, DbType.AnsiString }, + new object[]{ SqlDbType.Time, DbType.Time }, + new object[]{ SqlDbType.Timestamp, DbType.Binary }, + new object[]{ SqlDbType.TinyInt, DbType.Byte }, + new object[]{ SqlDbType.Udt, DbType.Object }, + new object[]{ SqlDbType.UniqueIdentifier, DbType.Guid }, + new object[]{ SqlDbType.VarBinary, DbType.Binary }, + new object[]{ SqlDbType.VarChar, DbType.AnsiString }, + new object[]{ SqlDbType.Variant, DbType.Object }, + new object[]{ SqlDbType.Xml, DbType.Xml } + }; + } + [Theory] - [InlineData(SqlDbType.BigInt, DbType.Int64)] - [InlineData(SqlDbType.Binary, DbType.Binary)] - [InlineData(SqlDbType.Bit, DbType.Boolean)] - [InlineData(SqlDbType.Char, DbType.AnsiStringFixedLength)] - [InlineData(SqlDbType.Date, DbType.Date)] - [InlineData(SqlDbType.DateTime, DbType.DateTime)] - [InlineData(SqlDbType.DateTimeOffset, DbType.DateTimeOffset)] - [InlineData(SqlDbType.Decimal, DbType.Decimal)] - [InlineData(SqlDbType.Float, DbType.Double)] - [InlineData(SqlDbType.Image, DbType.Binary)] - [InlineData(SqlDbType.Int, DbType.Int32)] - [InlineData(SqlDbType.Money, DbType.Currency)] - [InlineData(SqlDbType.NChar, DbType.StringFixedLength)] - [InlineData(SqlDbType.NText, DbType.String)] - [InlineData(SqlDbType.NVarChar, DbType.String)] - [InlineData(SqlDbType.Real, DbType.Single)] - [InlineData(SqlDbType.SmallDateTime, DbType.DateTime)] - [InlineData(SqlDbType.SmallInt, DbType.Int16)] - [InlineData(SqlDbType.SmallMoney, DbType.Currency)] - [InlineData(SqlDbType.Structured, DbType.Object)] - [InlineData(SqlDbType.Text, DbType.AnsiString)] - [InlineData(SqlDbType.Time, DbType.Time)] - [InlineData(SqlDbType.Timestamp, DbType.Binary)] - [InlineData(SqlDbType.TinyInt, DbType.Byte)] - [InlineData(SqlDbType.Udt, DbType.Object)] - [InlineData(SqlDbType.UniqueIdentifier, DbType.Guid)] - [InlineData(SqlDbType.VarBinary, DbType.Binary)] - [InlineData(SqlDbType.VarChar, DbType.AnsiString)] - [InlineData(SqlDbType.Variant, DbType.Object)] - [InlineData(SqlDbType.Xml, DbType.Xml)] + [MemberData( + nameof(Constructor3Data) +#if NETFRAMEWORK + // .NET Framework puts system enums in something called the Global + // Assembly Cache (GAC), and xUnit refuses to serialize enums that + // live there. So for .NET Framework, we disable enumeration of the + // test data to avoid warnings on the console when running tests. + , DisableDiscoveryEnumeration = true +#endif + )] public void Constructor3_Types(SqlDbType sqlDbType, DbType dbType) { SqlParameter p = new SqlParameter("types", sqlDbType); @@ -287,11 +307,24 @@ public void Constructor5(string value, string expected) Assert.Equal(expected, p.SourceColumn); } + public static IEnumerable Constructor6Data() + { + return new object[][] + { + new object[]{ ParameterDirection.Input, true, 0, 0, "", "", DataRowVersion.Current }, + new object[]{ ParameterDirection.InputOutput, true, 0, 0, null, "", DataRowVersion.Default }, + new object[]{ ParameterDirection.Output, false, 0, 255, "Col", "Col", DataRowVersion.Original }, + new object[]{ ParameterDirection.ReturnValue, false, 99, 100, " Col ", " Col ", DataRowVersion.Proposed } + }; + } + [Theory] - [InlineData(ParameterDirection.Input, true, 0, 0, "", "", DataRowVersion.Current)] - [InlineData(ParameterDirection.InputOutput, true, 0, 0, null, "", DataRowVersion.Default)] - [InlineData(ParameterDirection.Output, false, 0, 255, "Col", "Col", DataRowVersion.Original)] - [InlineData(ParameterDirection.ReturnValue, false, 99, 100, " Col ", " Col ", DataRowVersion.Proposed)] + [MemberData( + nameof(Constructor6Data) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void Constructor6(ParameterDirection parameterDirection, bool isNullable, byte precision, byte scale, string sourceColumn, string expectedSourceColumn, DataRowVersion dataRowVersion) { SqlParameter p = new SqlParameter("", SqlDbType.Bit, int.MaxValue, parameterDirection, isNullable, precision, scale, sourceColumn, dataRowVersion, null); @@ -304,11 +337,24 @@ public void Constructor6(ParameterDirection parameterDirection, bool isNullable, Assert.Equal(dataRowVersion, p.SourceVersion); } + public static IEnumerable Constructor7Data() + { + return new object[][] + { + new object[]{ ParameterDirection.Input, 0, 0, null, "", DataRowVersion.Current, false, null, "", null, "", null, "" }, + new object[]{ ParameterDirection.InputOutput, 255, 255, "", "", DataRowVersion.Default, true, "", "", "", "", "", "" }, + new object[]{ ParameterDirection.Output, 99, 100, "Source", "Source", DataRowVersion.Original, false, "aaa", "aaa", "bbb", "bbb", "ccc", "ccc" }, + new object[]{ ParameterDirection.ReturnValue, 100, 99, " Source ", " Source ", DataRowVersion.Proposed, true, " aaa ", " aaa ", " bbb ", " bbb ", " ccc ", " ccc " } + }; + } + [Theory] - [InlineData(ParameterDirection.Input, 0, 0, null, "", DataRowVersion.Current, false, null, "", null, "", null, "")] - [InlineData(ParameterDirection.InputOutput, 255, 255, "", "", DataRowVersion.Default, true, "", "", "", "", "", "")] - [InlineData(ParameterDirection.Output, 99, 100, "Source", "Source", DataRowVersion.Original, false, "aaa", "aaa", "bbb", "bbb", "ccc", "ccc")] - [InlineData(ParameterDirection.ReturnValue, 100, 99, " Source ", " Source ", DataRowVersion.Proposed, true, " aaa ", " aaa ", " bbb ", " bbb ", " ccc ", " ccc ")] + [MemberData( + nameof(Constructor7Data) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void Constructor7(ParameterDirection parameterDirection, byte precision, byte scale, string sourceColumn, string expectedSourceColumn, DataRowVersion dataRowVersion, bool sourceColumnNullMapping, string xmlSchemaCollectionDatabase, string expectedXmlSchemaCollectionDatabase, string xmlSchemaCollectionOwningSchema, string expectedXmlSchemaCollectionOwningSchema, string xmlSchemaCollectionName, string expectedXmlSchemaCollectionName) { SqlParameter p = new SqlParameter("", SqlDbType.Bit, int.MaxValue, parameterDirection, precision, scale, sourceColumn, dataRowVersion, sourceColumnNullMapping, null, xmlSchemaCollectionDatabase, xmlSchemaCollectionOwningSchema, xmlSchemaCollectionName); @@ -1010,28 +1056,41 @@ public void ResetDbType() Assert.Null(p.Value); } + public static IEnumerable ParameterSupportedData() + { + return new object[][] + { + new object[]{ DbType.AnsiString, SqlDbType.VarChar }, + new object[]{ DbType.AnsiStringFixedLength, SqlDbType.Char }, + new object[]{ DbType.Binary, SqlDbType.VarBinary }, + new object[]{ DbType.Boolean, SqlDbType.Bit }, + new object[]{ DbType.Byte, SqlDbType.TinyInt }, + new object[]{ DbType.Currency, SqlDbType.Money }, + new object[]{ DbType.Date, SqlDbType.Date }, + new object[]{ DbType.DateTime, SqlDbType.DateTime }, + new object[]{ DbType.DateTime2, SqlDbType.DateTime2 }, + new object[]{ DbType.DateTimeOffset, SqlDbType.DateTimeOffset }, + new object[]{ DbType.Decimal, SqlDbType.Decimal }, + new object[]{ DbType.Double, SqlDbType.Float }, + new object[]{ DbType.Guid, SqlDbType.UniqueIdentifier }, + new object[]{ DbType.Int16, SqlDbType.SmallInt }, + new object[]{ DbType.Int32, SqlDbType.Int }, + new object[]{ DbType.Int64, SqlDbType.BigInt }, + new object[]{ DbType.Object, SqlDbType.Variant }, + new object[]{ DbType.Single, SqlDbType.Real }, + new object[]{ DbType.String, SqlDbType.NVarChar }, + new object[]{ DbType.Time, SqlDbType.Time }, + new object[]{ DbType.Xml, SqlDbType.Xml } + }; + } + [Theory] - [InlineData(DbType.AnsiString, SqlDbType.VarChar)] - [InlineData(DbType.AnsiStringFixedLength, SqlDbType.Char)] - [InlineData(DbType.Binary, SqlDbType.VarBinary)] - [InlineData(DbType.Boolean, SqlDbType.Bit)] - [InlineData(DbType.Byte, SqlDbType.TinyInt)] - [InlineData(DbType.Currency, SqlDbType.Money)] - [InlineData(DbType.Date, SqlDbType.Date)] - [InlineData(DbType.DateTime, SqlDbType.DateTime)] - [InlineData(DbType.DateTime2, SqlDbType.DateTime2)] - [InlineData(DbType.DateTimeOffset, SqlDbType.DateTimeOffset)] - [InlineData(DbType.Decimal, SqlDbType.Decimal)] - [InlineData(DbType.Double, SqlDbType.Float)] - [InlineData(DbType.Guid, SqlDbType.UniqueIdentifier)] - [InlineData(DbType.Int16, SqlDbType.SmallInt)] - [InlineData(DbType.Int32, SqlDbType.Int)] - [InlineData(DbType.Int64, SqlDbType.BigInt)] - [InlineData(DbType.Object, SqlDbType.Variant)] - [InlineData(DbType.Single, SqlDbType.Real)] - [InlineData(DbType.String, SqlDbType.NVarChar)] - [InlineData(DbType.Time, SqlDbType.Time)] - [InlineData(DbType.Xml, SqlDbType.Xml)] + [MemberData( + nameof(ParameterSupportedData) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void Parameter_Supported(DbType dbType, SqlDbType sqlDbType) { var parameter = new SqlParameter(); @@ -1040,12 +1099,25 @@ public void Parameter_Supported(DbType dbType, SqlDbType sqlDbType) Assert.Equal(sqlDbType, parameter.SqlDbType); } + public static IEnumerable ParameterNotSupportedData() + { + return new object[][] + { + new object[]{ DbType.SByte }, + new object[]{ DbType.UInt16 }, + new object[]{ DbType.UInt32 }, + new object[]{ DbType.UInt64 }, + new object[]{ DbType.VarNumeric } + }; + } + [Theory] - [InlineData(DbType.SByte)] - [InlineData(DbType.UInt16)] - [InlineData(DbType.UInt32)] - [InlineData(DbType.UInt64)] - [InlineData(DbType.VarNumeric)] + [MemberData( + nameof(ParameterNotSupportedData) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void Parameter_NotSupported(DbType dbType) { var parameter = new SqlParameter(); @@ -1875,89 +1947,29 @@ private enum Int64Enum : long [InlineData(5, 5, false)] [InlineData(6, 6, false)] [InlineData(7, 7, false)] - [InlineData(null, 7, null)] - [InlineData(0, 7, null)] - [InlineData(1, 1, null)] - [InlineData(2, 2, null)] - [InlineData(3, 3, null)] - [InlineData(4, 4, null)] - [InlineData(5, 5, null)] - [InlineData(6, 6, null)] - [InlineData(7, 7, null)] - public void SqlDatetime2Scale_Legacy(int? setScale, byte outputScale, bool? legacyVarTimeZeroScaleSwitchValue) + public void SqlDateTime2Scale_Legacy(int? setScale, byte outputScale, bool legacyVarTimeZeroScaleSwitchValue) { lock (_parameterLegacyScaleLock) { - var originalLegacyVarTimeZeroScaleSwitchValue = SetLegacyVarTimeZeroScaleBehaviour(legacyVarTimeZeroScaleSwitchValue); - try - { - var parameter = new SqlParameter - { - DbType = DbType.DateTime2 - }; - if (setScale.HasValue) - { - parameter.Scale = (byte)setScale.Value; - } - - var actualScale = (byte)typeof(SqlParameter).GetMethod("GetActualScale", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(parameter, null); - - Assert.Equal(outputScale, actualScale); - } + using SwitchesHelper switches = new SwitchesHelper(); + switches.LegacyVarTimeZeroScaleBehaviourField = + legacyVarTimeZeroScaleSwitchValue + ? SwitchesHelper.Tristate.True + : SwitchesHelper.Tristate.False; - finally + var parameter = new SqlParameter { - SetLegacyVarTimeZeroScaleBehaviour(originalLegacyVarTimeZeroScaleSwitchValue); + DbType = DbType.DateTime2 + }; + if (setScale.HasValue) + { + parameter.Scale = (byte)setScale.Value; } - } - } - - [Fact] - public void SetLegacyVarTimeZeroScaleBehaviour_Defaults_to_True() - { - var legacyVarTimeZeroScaleBehaviour = (bool)LocalAppContextSwitchesType.GetProperty("LegacyVarTimeZeroScaleBehaviour", BindingFlags.Public | BindingFlags.Static).GetValue(null); - - Assert.True(legacyVarTimeZeroScaleBehaviour); - } - - private static Type LocalAppContextSwitchesType => typeof(SqlCommand).Assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches"); - private static bool? SetLegacyVarTimeZeroScaleBehaviour(bool? value) - { - const string LegacyVarTimeZeroScaleBehaviourSwitchname = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour"; - - //reset internal state to "NotInitialized" so we pick up the value via AppContext - FieldInfo switchField = LocalAppContextSwitchesType.GetField("s_legacyVarTimeZeroScaleBehaviour", BindingFlags.NonPublic | BindingFlags.Static); - switchField.SetValue(null, (byte)0); + var actualScale = (byte)typeof(SqlParameter).GetMethod("GetActualScale", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(parameter, null); - bool? returnValue = null; - if (AppContext.TryGetSwitch(LegacyVarTimeZeroScaleBehaviourSwitchname, out var originalValue)) - { - returnValue = originalValue; + Assert.Equal(outputScale, actualScale); } - - if (value.HasValue) - { - AppContext.SetSwitch(LegacyVarTimeZeroScaleBehaviourSwitchname, value.Value); - } - else - { - //need to remove the switch value via reflection as AppContext does not expose a means to do that. -#if NET - var switches = typeof(AppContext).GetField("s_switches", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null); - if (switches is not null) //may be null if not initialised yet - { - MethodInfo removeMethod = switches.GetType().GetMethod("Remove", BindingFlags.Public | BindingFlags.Instance, new Type[] { typeof(string) }); - removeMethod.Invoke(switches, new[] { LegacyVarTimeZeroScaleBehaviourSwitchname }); - } -#else - var switches = typeof(AppContext).GetField("s_switchMap", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null); - MethodInfo removeMethod = switches.GetType().GetMethod("Remove", BindingFlags.Public | BindingFlags.Instance); - removeMethod.Invoke(switches, new[] { LegacyVarTimeZeroScaleBehaviourSwitchname }); -#endif - } - - return returnValue; } } } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestRoutingTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestRoutingTdsServer.cs index 275cb58035..130b50cad9 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestRoutingTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestRoutingTdsServer.cs @@ -42,7 +42,7 @@ public static TestRoutingTdsServer StartTestServer(IPEndPoint destinationEndpoin server._endpoint = new TDSServerEndPoint(server) { ServerEndPoint = new IPEndPoint(IPAddress.Any, 0) }; server._endpoint.EndpointName = methodName; // The server EventLog should be enabled as it logs the exceptions. - server._endpoint.EventLog = Console.Out; + server._endpoint.EventLog = enableLog ? Console.Out : null; server._endpoint.Start(); int port = server._endpoint.ServerEndPoint.Port; diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs index e383ac4dc6..a5976fd6d5 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TestTdsServer.cs @@ -25,7 +25,7 @@ public TestTdsServer(QueryEngine engine, TDSServerArguments args) : base(args) Engine = engine; } - public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, bool excludeEncryption = false, [CallerMemberName] string methodName = "") + public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, bool excludeEncryption = false, Version serverVersion = null, [CallerMemberName] string methodName = "") { TDSServerArguments args = new TDSServerArguments() { @@ -40,12 +40,16 @@ public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool { args.Encryption = SqlServer.TDS.PreLogin.TDSPreLoginTokenEncryptionType.None; } + if (serverVersion != null) + { + args.ServerVersion = serverVersion; + } TestTdsServer server = engine == null ? new TestTdsServer(args) : new TestTdsServer(engine, args); server._endpoint = new TDSServerEndPoint(server) { ServerEndPoint = new IPEndPoint(IPAddress.Any, 0) }; server._endpoint.EndpointName = methodName; // The server EventLog should be enabled as it logs the exceptions. - server._endpoint.EventLog = Console.Out; + server._endpoint.EventLog = enableLog ? Console.Out : null; server._endpoint.Start(); int port = server._endpoint.ServerEndPoint.Port; @@ -58,9 +62,9 @@ public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool return server; } - public static TestTdsServer StartTestServer(bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, bool excludeEncryption = false, [CallerMemberName] string methodName = "") + public static TestTdsServer StartTestServer(bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, bool excludeEncryption = false, Version serverVersion = null, [CallerMemberName] string methodName = "") { - return StartServerWithQueryEngine(null, enableFedAuth, enableLog, connectionTimeout, excludeEncryption, methodName); + return StartServerWithQueryEngine(null, enableFedAuth, enableLog, connectionTimeout, excludeEncryption, serverVersion, methodName); } public void Dispose() => _endpoint?.Stop(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs index f39dbb3bea..78fa0a9001 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVTests.cs @@ -133,15 +133,15 @@ public void ForcedColumnDecryptErrorTestShouldFail() [PlatformSpecific(TestPlatforms.Windows)] public void TestRoundTripWithAKVAndCertStoreProvider() { - using SQLSetupStrategyCertStoreProvider certStoreFixture = new(); + SqlColumnEncryptionCertificateStoreProvider certStoreProvider = new SqlColumnEncryptionCertificateStoreProvider(); byte[] plainTextColumnEncryptionKey = ColumnEncryptionKey.GenerateRandomBytes(ColumnEncryptionKey.KeySizeInBytes); - byte[] encryptedColumnEncryptionKeyUsingAKV = _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, @"RSA_OAEP", plainTextColumnEncryptionKey); - byte[] columnEncryptionKeyReturnedAKV2Cert = certStoreFixture.CertStoreProvider.DecryptColumnEncryptionKey(certStoreFixture.CspColumnMasterKey.KeyPath, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingAKV); + byte[] encryptedColumnEncryptionKeyUsingAKV = _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(_fixture.AkvKeyUrl, @"RSA_OAEP", plainTextColumnEncryptionKey); + byte[] columnEncryptionKeyReturnedAKV2Cert = certStoreProvider.DecryptColumnEncryptionKey(_fixture.ColumnMasterKeyPath, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingAKV); Assert.True(plainTextColumnEncryptionKey.SequenceEqual(columnEncryptionKeyReturnedAKV2Cert), @"Roundtrip failed"); // Try the opposite. - byte[] encryptedColumnEncryptionKeyUsingCert = certStoreFixture.CertStoreProvider.EncryptColumnEncryptionKey(certStoreFixture.CspColumnMasterKey.KeyPath, @"RSA_OAEP", plainTextColumnEncryptionKey); - byte[] columnEncryptionKeyReturnedCert2AKV = _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingCert); + byte[] encryptedColumnEncryptionKeyUsingCert = certStoreProvider.EncryptColumnEncryptionKey(_fixture.ColumnMasterKeyPath, @"RSA_OAEP", plainTextColumnEncryptionKey); + byte[] columnEncryptionKeyReturnedCert2AKV = _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingCert); Assert.True(plainTextColumnEncryptionKey.SequenceEqual(columnEncryptionKeyReturnedCert2AKV), @"Roundtrip failed"); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVUnitTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVUnitTests.cs index 9374b3783c..2111c7a1af 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVUnitTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/AKVUnitTests.cs @@ -14,13 +14,20 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { - public static class AKVUnitTests + public class AKVUnitTests : IClassFixture { const string EncryptionAlgorithm = "RSA_OAEP"; public static readonly byte[] s_columnEncryptionKey = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 }; private const string cekCacheName = "_columnEncryptionKeyCache"; private const string signatureVerificationResultCacheName = "_columnMasterKeyMetadataSignatureVerificationCache"; + private readonly AzureKeyVaultKeyFixture _fixture; + + public AKVUnitTests(AzureKeyVaultKeyFixture fixture) + { + _fixture = fixture; + } + private static void ValidateAKVTraces(List eventData, Guid threadActivityId) { Assert.NotNull(eventData); @@ -64,36 +71,36 @@ private static void ValidateAKVTraces(List eventData, Gui } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] - public static void LegacyAuthenticationCallbackTest() + public void LegacyAuthenticationCallbackTest() { Guid activityId = Trace.CorrelationManager.ActivityId = Guid.NewGuid(); using DataTestUtility.AKVEventListener AKVListener = new(); // SqlClientCustomTokenCredential implements legacy authentication callback to request access token at client-side. SqlColumnEncryptionAzureKeyVaultProvider akvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new SqlClientCustomTokenCredential()); - byte[] encryptedCek = akvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, s_columnEncryptionKey); - byte[] decryptedCek = akvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, encryptedCek); + byte[] encryptedCek = akvProvider.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, EncryptionAlgorithm, s_columnEncryptionKey); + byte[] decryptedCek = akvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, EncryptionAlgorithm, encryptedCek); Assert.Equal(s_columnEncryptionKey, decryptedCek); ValidateAKVTraces(AKVListener.EventData, activityId); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] - public static void TokenCredentialTest() + public void TokenCredentialTest() { Guid activityId = Trace.CorrelationManager.ActivityId = Guid.NewGuid(); using DataTestUtility.AKVEventListener AKVListener = new(); SqlColumnEncryptionAzureKeyVaultProvider akvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(DataTestUtility.GetTokenCredential()); - byte[] encryptedCek = akvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, s_columnEncryptionKey); - byte[] decryptedCek = akvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, encryptedCek); + byte[] encryptedCek = akvProvider.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, EncryptionAlgorithm, s_columnEncryptionKey); + byte[] decryptedCek = akvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, EncryptionAlgorithm, encryptedCek); Assert.Equal(s_columnEncryptionKey, decryptedCek); ValidateAKVTraces(AKVListener.EventData, activityId); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] - public static void TokenCredentialRotationTest() + public void TokenCredentialRotationTest() { Guid activityId = Trace.CorrelationManager.ActivityId = Guid.NewGuid(); using DataTestUtility.AKVEventListener AKVListener = new(); @@ -103,19 +110,19 @@ public static void TokenCredentialRotationTest() SqlColumnEncryptionAzureKeyVaultProvider newAkvProvider = new SqlColumnEncryptionAzureKeyVaultProvider(DataTestUtility.GetTokenCredential()); - byte[] encryptedCekWithNewProvider = newAkvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, s_columnEncryptionKey); - byte[] decryptedCekWithOldProvider = oldAkvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, encryptedCekWithNewProvider); + byte[] encryptedCekWithNewProvider = newAkvProvider.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, EncryptionAlgorithm, s_columnEncryptionKey); + byte[] decryptedCekWithOldProvider = oldAkvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, EncryptionAlgorithm, encryptedCekWithNewProvider); Assert.Equal(s_columnEncryptionKey, decryptedCekWithOldProvider); - byte[] encryptedCekWithOldProvider = oldAkvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, s_columnEncryptionKey); - byte[] decryptedCekWithNewProvider = newAkvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, EncryptionAlgorithm, encryptedCekWithOldProvider); + byte[] encryptedCekWithOldProvider = oldAkvProvider.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, EncryptionAlgorithm, s_columnEncryptionKey); + byte[] decryptedCekWithNewProvider = newAkvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, EncryptionAlgorithm, encryptedCekWithOldProvider); Assert.Equal(s_columnEncryptionKey, decryptedCekWithNewProvider); ValidateAKVTraces(AKVListener.EventData, activityId); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] - public static void ReturnSpecifiedVersionOfKeyWhenItIsNotTheMostRecentVersion() + public void ReturnSpecifiedVersionOfKeyWhenItIsNotTheMostRecentVersion() { Uri keyPathUri = new Uri(DataTestUtility.AKVOriginalUrl); Uri vaultUri = new Uri(keyPathUri.GetLeftPart(UriPartial.Authority)); @@ -161,7 +168,7 @@ public static void ThrowWhenUrlHasLessThanThreeSegments() } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] - public static void DecryptedCekIsCachedDuringDecryption() + public void DecryptedCekIsCachedDuringDecryption() { Guid activityId = Trace.CorrelationManager.ActivityId = Guid.NewGuid(); using DataTestUtility.AKVEventListener AKVListener = new(); @@ -170,23 +177,23 @@ public static void DecryptedCekIsCachedDuringDecryption() byte[] plaintextKey1 = { 1, 2, 3 }; byte[] plaintextKey2 = { 1, 2, 3 }; byte[] plaintextKey3 = { 0, 1, 2, 3 }; - byte[] encryptedKey1 = akvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", plaintextKey1); - byte[] encryptedKey2 = akvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", plaintextKey2); - byte[] encryptedKey3 = akvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", plaintextKey3); + byte[] encryptedKey1 = akvProvider.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", plaintextKey1); + byte[] encryptedKey2 = akvProvider.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", plaintextKey2); + byte[] encryptedKey3 = akvProvider.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", plaintextKey3); - byte[] decryptedKey1 = akvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", encryptedKey1); + byte[] decryptedKey1 = akvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", encryptedKey1); Assert.Equal(1, GetCacheCount(cekCacheName, akvProvider)); Assert.Equal(plaintextKey1, decryptedKey1); - decryptedKey1 = akvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", encryptedKey1); + decryptedKey1 = akvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", encryptedKey1); Assert.Equal(1, GetCacheCount(cekCacheName, akvProvider)); Assert.Equal(plaintextKey1, decryptedKey1); - byte[] decryptedKey2 = akvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", encryptedKey2); + byte[] decryptedKey2 = akvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", encryptedKey2); Assert.Equal(2, GetCacheCount(cekCacheName, akvProvider)); Assert.Equal(plaintextKey2, decryptedKey2); - byte[] decryptedKey3 = akvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", encryptedKey3); + byte[] decryptedKey3 = akvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", encryptedKey3); Assert.Equal(3, GetCacheCount(cekCacheName, akvProvider)); Assert.Equal(plaintextKey3, decryptedKey3); @@ -194,33 +201,33 @@ public static void DecryptedCekIsCachedDuringDecryption() } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] - public static void SignatureVerificationResultIsCachedDuringVerification() + public void SignatureVerificationResultIsCachedDuringVerification() { Guid activityId = Trace.CorrelationManager.ActivityId = Guid.NewGuid(); using DataTestUtility.AKVEventListener AKVListener = new(); SqlColumnEncryptionAzureKeyVaultProvider akvProvider = new(new SqlClientCustomTokenCredential()); - byte[] signature = akvProvider.SignColumnMasterKeyMetadata(DataTestUtility.AKVUrl, true); - byte[] signature2 = akvProvider.SignColumnMasterKeyMetadata(DataTestUtility.AKVUrl, true); - byte[] signatureWithoutEnclave = akvProvider.SignColumnMasterKeyMetadata(DataTestUtility.AKVUrl, false); + byte[] signature = akvProvider.SignColumnMasterKeyMetadata(_fixture.GeneratedKeyUri, true); + byte[] signature2 = akvProvider.SignColumnMasterKeyMetadata(_fixture.GeneratedKeyUri, true); + byte[] signatureWithoutEnclave = akvProvider.SignColumnMasterKeyMetadata(_fixture.GeneratedKeyUri, false); - Assert.True(akvProvider.VerifyColumnMasterKeyMetadata(DataTestUtility.AKVUrl, true, signature)); + Assert.True(akvProvider.VerifyColumnMasterKeyMetadata(_fixture.GeneratedKeyUri, true, signature)); Assert.Equal(1, GetCacheCount(signatureVerificationResultCacheName, akvProvider)); - Assert.True(akvProvider.VerifyColumnMasterKeyMetadata(DataTestUtility.AKVUrl, true, signature)); + Assert.True(akvProvider.VerifyColumnMasterKeyMetadata(_fixture.GeneratedKeyUri, true, signature)); Assert.Equal(1, GetCacheCount(signatureVerificationResultCacheName, akvProvider)); - Assert.True(akvProvider.VerifyColumnMasterKeyMetadata(DataTestUtility.AKVUrl, true, signature2)); + Assert.True(akvProvider.VerifyColumnMasterKeyMetadata(_fixture.GeneratedKeyUri, true, signature2)); Assert.Equal(1, GetCacheCount(signatureVerificationResultCacheName, akvProvider)); - Assert.True(akvProvider.VerifyColumnMasterKeyMetadata(DataTestUtility.AKVUrl, false, signatureWithoutEnclave)); + Assert.True(akvProvider.VerifyColumnMasterKeyMetadata(_fixture.GeneratedKeyUri, false, signatureWithoutEnclave)); Assert.Equal(2, GetCacheCount(signatureVerificationResultCacheName, akvProvider)); ValidateAKVTraces(AKVListener.EventData, activityId); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] - public static void CekCacheEntryIsEvictedAfterTtlExpires() + public void CekCacheEntryIsEvictedAfterTtlExpires() { Guid activityId = Trace.CorrelationManager.ActivityId = Guid.NewGuid(); using DataTestUtility.AKVEventListener AKVListener = new(); @@ -228,9 +235,9 @@ public static void CekCacheEntryIsEvictedAfterTtlExpires() SqlColumnEncryptionAzureKeyVaultProvider akvProvider = new(new SqlClientCustomTokenCredential()); akvProvider.ColumnEncryptionKeyCacheTtl = TimeSpan.FromSeconds(5); byte[] plaintextKey = { 1, 2, 3 }; - byte[] encryptedKey = akvProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", plaintextKey); + byte[] encryptedKey = akvProvider.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", plaintextKey); - akvProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", encryptedKey); + akvProvider.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", encryptedKey); Assert.True(CekCacheContainsKey(encryptedKey, akvProvider)); Assert.Equal(1, GetCacheCount(cekCacheName, akvProvider)); @@ -242,7 +249,7 @@ public static void CekCacheEntryIsEvictedAfterTtlExpires() } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] - public static void CekCacheShouldBeDisabledWhenCustomProviderIsRegisteredGlobally() + public void CekCacheShouldBeDisabledWhenCustomProviderIsRegisteredGlobally() { if (SQLSetupStrategyAzureKeyVault.IsAKVProviderRegistered) { @@ -255,9 +262,9 @@ public static void CekCacheShouldBeDisabledWhenCustomProviderIsRegisteredGloball SqlColumnEncryptionAzureKeyVaultProvider akvProviderInGlobalCache = globalProviders["AZURE_KEY_VAULT"] as SqlColumnEncryptionAzureKeyVaultProvider; byte[] plaintextKey = { 1, 2, 3 }; - byte[] encryptedKey = akvProviderInGlobalCache.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", plaintextKey); + byte[] encryptedKey = akvProviderInGlobalCache.EncryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", plaintextKey); - akvProviderInGlobalCache.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_OAEP", encryptedKey); + akvProviderInGlobalCache.DecryptColumnEncryptionKey(_fixture.GeneratedKeyUri, "RSA_OAEP", encryptedKey); Assert.Equal(0, GetCacheCount(cekCacheName, akvProviderInGlobalCache)); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs index fc65ecabf5..1b17a0f4ef 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs @@ -45,6 +45,24 @@ public sealed class ApiShould : IClassFixture, IDis "'MSSQL_CERTIFICATE_STORE', 'MSSQL_CNG_STORE', 'MSSQL_CSP_PROVIDER'", $"'{NotRequiredProviderName}'"); + private HashSet _cancellationExceptionMessages = new HashSet() + { + "A severe error occurred on the current command. " + + "The results, if any, should be discarded." + Environment.NewLine + + "Operation cancelled by user.", + + "A severe error occurred on the current command. " + + "The results, if any, should be discarded.", + + "Operation cancelled by user.", + + "The request failed to run because the batch is aborted, " + + "this can be caused by abort signal sent from client, " + + "or another request is running in the same session, " + + "which makes the session busy." + Environment.NewLine + + "Operation cancelled by user." + }; + public ApiShould(PlatformSpecificTestContext context) { _fixture = context.Fixture; @@ -425,7 +443,17 @@ public void TestSqlDataAdapterFillDataTable(string connection) } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] - [ClassData(typeof(AEConnectionStringProviderWithSchemaType))] + [MemberData( + nameof(DataHelpers.AEConnectionStringProviderWithSchemaType), + MemberType = typeof(DataHelpers) +#if NETFRAMEWORK + // .NET Framework puts system enums in something called the Global + // Assembly Cache (GAC), and xUnit refuses to serialize enums that + // live there. So for .NET Framework, we disable enumeration of the + // test data to avoid warnings on the console when running tests. + , DisableDiscoveryEnumeration = true +#endif + )] public void TestSqlDataAdapterFillSchema(string connection, SchemaType schemaType) { CleanUpTable(connection, _tableName); @@ -744,7 +772,13 @@ public async Task TestExecuteReaderAsyncWithLargeQuery(string connectionString) } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] - [ClassData(typeof(AEConnectionStringProviderWithCommandBehaviorSet1))] + [MemberData( + nameof(DataHelpers.AEConnectionStringProviderWithCommandBehaviorSet1), + MemberType = typeof(DataHelpers) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void TestExecuteReaderWithCommandBehavior(string connection, CommandBehavior commandBehavior) { string[] columnNames = new string[3] { "CustomerId", "FirstName", "LastName" }; @@ -1141,7 +1175,13 @@ public void TestPrepareWithExecuteNonQueryAsync(string connection) } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] - [ClassData(typeof(AEConnectionStringProviderWithCommandBehaviorSet2))] + [MemberData( + nameof(DataHelpers.AEConnectionStringProviderWithCommandBehaviorSet2), + MemberType = typeof(DataHelpers) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void TestPrepareWithExecuteReaderAsync(string connection, CommandBehavior commandBehavior) { CleanUpTable(connection, _tableName); @@ -1828,7 +1868,13 @@ public void TestSqlDataReaderAPIsWithSequentialAccess(string connection) } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] - [ClassData(typeof(AEConnectionStringProviderWithCommandBehaviorSet2))] + [MemberData( + nameof(DataHelpers.AEConnectionStringProviderWithCommandBehaviorSet2), + MemberType = typeof(DataHelpers) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void TestSqlCommandSequentialAccessCodePaths(string connection, CommandBehavior value) { CleanUpTable(connection, _tableName); @@ -1913,7 +1959,13 @@ public void TestExecuteXmlReader(string connection) [ActiveIssue("10620")] // Randomly hangs the process. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] - [ClassData(typeof(AEConnectionStringProviderWithCommandBehaviorSet1))] + [MemberData( + nameof(DataHelpers.AEConnectionStringProviderWithCommandBehaviorSet1), + MemberType = typeof(DataHelpers) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void TestBeginAndEndExecuteReaderWithAsyncCallback(string connection, CommandBehavior commandbehavior) { CleanUpTable(connection, _tableName); @@ -1949,19 +2001,15 @@ public void TestBeginAndEndExecuteReaderWithAsyncCallback(string connection, Com } } - [SkipOnTargetFramework(TargetFrameworkMonikers.Netcoreapp)] [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] [ClassData(typeof(AEConnectionStringProviderWithExecutionMethod))] - public void TestSqlCommandCancel(string connection, string value, int number) + public void TestSqlCommandCancel(string connection, string value) { CleanUpTable(connection, _tableName); string executeMethod = value; Assert.True(!string.IsNullOrWhiteSpace(executeMethod), @"executeMethod should not be null or empty"); - int numberOfCancelCalls = number; - Assert.True(numberOfCancelCalls >= 0, "numberofCancelCalls should be >=0."); - IList values = GetValues(dataHint: 58); Assert.True(values != null && values.Count >= 3, @"values should not be null and count should be >= 3."); @@ -1973,7 +2021,10 @@ public void TestSqlCommandCancel(string connection, string value, int number) { sqlConnection.Open(); - using (SqlCommand sqlCommand = new SqlCommand($@"SELECT * FROM [{_tableName}] WHERE FirstName = @FirstName AND CustomerId = @CustomerId", + // WAITFOR DELAY is present to ensure that the command is always executing by the time the cancellation occurs. + // Without this, a command which has completed by the time the cancellation occurs will fail the test. It's last + // in the command to ensure that cancellation is tested while row data or metadata is flowing. + using (SqlCommand sqlCommand = new SqlCommand($@"SELECT * FROM [{_tableName}] WHERE FirstName = @FirstName AND CustomerId = @CustomerId; WAITFOR DELAY '00:00:00.150'", sqlConnection, transaction: null, columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled)) @@ -1983,29 +2034,50 @@ public void TestSqlCommandCancel(string connection, string value, int number) CommandHelper.s_sleepDuringTryFetchInputParameterEncryptionInfo?.SetValue(null, true); - Thread[] threads = new Thread[2]; - - // Invoke ExecuteReader or ExecuteNonQuery in another thread. + Task[] tasks = new Task[2]; + ManualResetEventSlim startWorkloadSignal = new ManualResetEventSlim(false); + ManualResetEventSlim workloadCompleteSignal = new ManualResetEventSlim(false); + + /* + * Invoke ExecuteReader or ExecuteNonQuery in another thread. + * Use long-running tasks to create the thread. This enables any failed assertions to propagate, rather than + * allowing the exception to kill the thread and the process. + * These threads should progress in the sequence below: + * + * Workload Thread | Cancel Thread + * ------------------------------------ | ------------- + * Start thread | Start thread + * Wait for signal | - + * - | Set signal for workload start + * Start workload execution | Loop, cancelling workload until workload complete signal set + * Throw cancellation exception | - + * Set signal for workload complete | - + * End thread | Finish loop and end thread + */ if (executeMethod == @"ExecuteReader") { - threads[0] = new Thread(new ParameterizedThreadStart(Thread_ExecuteReader)); + tasks[0] = new Task(Thread_ExecuteReader, + new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal), + TaskCreationOptions.LongRunning); } else { - threads[0] = new Thread(new ParameterizedThreadStart(Thread_ExecuteNonQuery)); + tasks[0] = new Task(Thread_ExecuteNonQuery, + new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal), + TaskCreationOptions.LongRunning); } - - threads[1] = new Thread(new ParameterizedThreadStart(Thread_Cancel)); + tasks[1] = new Task(Thread_Cancel, + new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal), + TaskCreationOptions.LongRunning); // Start the execute thread. - threads[0].Start(new TestCommandCancelParams(sqlCommand, _tableName, numberOfCancelCalls)); + tasks[0].Start(); // Start the thread which cancels the above command started by the execute thread. - threads[1].Start(new TestCommandCancelParams(sqlCommand, _tableName, numberOfCancelCalls)); + tasks[1].Start(); // Wait for the threads to finish. - threads[0].Join(); - threads[1].Join(); + Task.WaitAll(tasks); CommandHelper.s_sleepDuringTryFetchInputParameterEncryptionInfo?.SetValue(null, false); @@ -2027,37 +2099,48 @@ public void TestSqlCommandCancel(string connection, string value, int number) Assert.True(rowsAffected == numberOfRows, "Unexpected number of rows affected as returned by EndExecuteReader."); - // Verify the state of the sql command object. + // Verify the state of the sql command object. Also ensure that the exit lock was set (and didn't time out.) VerifySqlCommandStateAfterCompletionOrCancel(sqlCommand); + Assert.True(workloadCompleteSignal.IsSet); + startWorkloadSignal.Reset(); + workloadCompleteSignal.Reset(); CommandHelper.s_sleepDuringRunExecuteReaderTdsForSpDescribeParameterEncryption?.SetValue(null, true); // Invoke ExecuteReader or ExecuteNonQuery in another thread. - threads = new Thread[2]; + tasks = new Task[2]; if (executeMethod == @"ExecuteReader") { - threads[0] = new Thread(new ParameterizedThreadStart(Thread_ExecuteReader)); + tasks[0] = new Task(Thread_ExecuteReader, + new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal), + TaskCreationOptions.LongRunning); } else { - threads[0] = new Thread(new ParameterizedThreadStart(Thread_ExecuteNonQuery)); + tasks[0] = new Task(Thread_ExecuteNonQuery, + new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal), + TaskCreationOptions.LongRunning); } - threads[1] = new Thread(new ParameterizedThreadStart(Thread_Cancel)); + tasks[1] = new Task(Thread_Cancel, + new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal), + TaskCreationOptions.LongRunning); // Start the execute thread. - threads[0].Start(new TestCommandCancelParams(sqlCommand, _tableName, numberOfCancelCalls)); + tasks[0].Start(); // Start the thread which cancels the above command started by the execute thread. - threads[1].Start(new TestCommandCancelParams(sqlCommand, _tableName, numberOfCancelCalls)); + tasks[1].Start(); // Wait for the threads to finish. - threads[0].Join(); - threads[1].Join(); + Task.WaitAll(tasks); CommandHelper.s_sleepDuringRunExecuteReaderTdsForSpDescribeParameterEncryption?.SetValue(null, false); - // Verify the state of the sql command object. + // Verify the state of the sql command object. Also ensure that the exit lock was set (and didn't time out.) VerifySqlCommandStateAfterCompletionOrCancel(sqlCommand); + Assert.True(workloadCompleteSignal.IsSet); + startWorkloadSignal.Reset(); + workloadCompleteSignal.Reset(); rowsAffected = 0; @@ -2079,31 +2162,37 @@ public void TestSqlCommandCancel(string connection, string value, int number) CommandHelper.s_sleepAfterReadDescribeEncryptionParameterResults?.SetValue(null, true); - threads = new Thread[2]; + tasks = new Task[2]; if (executeMethod == @"ExecuteReader") { - threads[0] = new Thread(new ParameterizedThreadStart(Thread_ExecuteReader)); + tasks[0] = new Task(Thread_ExecuteReader, + new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal), + TaskCreationOptions.LongRunning); } else { - threads[0] = new Thread(new ParameterizedThreadStart(Thread_ExecuteNonQuery)); + tasks[0] = new Task(Thread_ExecuteNonQuery, + new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal), + TaskCreationOptions.LongRunning); } - threads[1] = new Thread(new ParameterizedThreadStart(Thread_Cancel)); + tasks[1] = new Task(Thread_Cancel, + new TestCommandCancelParams(sqlCommand, _tableName, startWorkloadSignal, workloadCompleteSignal), + TaskCreationOptions.LongRunning); // Start the execute thread. - threads[0].Start(new TestCommandCancelParams(sqlCommand, _tableName, numberOfCancelCalls)); + tasks[0].Start(); // Start the thread which cancels the above command started by the execute thread. - threads[1].Start(new TestCommandCancelParams(sqlCommand, _tableName, numberOfCancelCalls)); + tasks[1].Start(); // Wait for the threads to finish. - threads[0].Join(); - threads[1].Join(); + Task.WaitAll(tasks); CommandHelper.s_sleepAfterReadDescribeEncryptionParameterResults?.SetValue(null, false); - // Verify the state of the sql command object. + // Verify the state of the sql command object. Also ensure that the exit lock was set (and didn't time out.) VerifySqlCommandStateAfterCompletionOrCancel(sqlCommand); + Assert.True(workloadCompleteSignal.IsSet); rowsAffected = 0; @@ -3096,47 +3185,97 @@ private void TestCancellationToken(FieldInfo failpoint, SqlCommand sqlCommand, i Assert.True(rowsAffected == numberOfRows, "Unexpected number of rows affected as returned by EndExecuteReader."); } - private void Thread_ExecuteReader(object cancelCommandTestParamsObject) + private void Thread_ExecuteReader(object state) { + TestCommandCancelParams cancelCommandTestParamsObject = state as TestCommandCancelParams; + SqlCommand sqlCommand = cancelCommandTestParamsObject?.SqlCommand; + SqlDataReader reader = null; + Assert.True(cancelCommandTestParamsObject != null, @"cancelCommandTestParamsObject should not be null."); - SqlCommand sqlCommand = ((TestCommandCancelParams)cancelCommandTestParamsObject).SqlCommand as SqlCommand; Assert.True(sqlCommand != null, "sqlCommand should not be null."); - string.Format(@"SELECT * FROM {0} WHERE FirstName = @FirstName AND CustomerId = @CustomerId", ((TestCommandCancelParams)cancelCommandTestParamsObject).TableName); - using (SqlDataReader reader = sqlCommand.ExecuteReader()) + try { - while (reader.Read()) + // Wait for the cancellation thread to open this lock... + cancelCommandTestParamsObject.StartWorkloadSignal.Wait(); + + Exception ex = Assert.ThrowsAny(() => + { + reader = sqlCommand.ExecuteReader(); + while (reader.Read()) + { } + }); + + // We don't use Assert.Contains() here because it truncates the + // actual and expected strings when outputting a failure. + if (!_cancellationExceptionMessages.Contains(ex.Message)) { - Assert.Throws(() => sqlCommand.ExecuteReader()); + Assert.Fail( + $"Exception message \"{ex.Message}\" not found in: [\"" + + string.Join("\", \"", _cancellationExceptionMessages) + "\"]"); } } + finally + { + reader?.Dispose(); + // ...and unlock the cancellation thread once we finish. + cancelCommandTestParamsObject.WorkloadCompleteSignal.Set(); + } } - private void Thread_ExecuteNonQuery(object cancelCommandTestParamsObject) + private void Thread_ExecuteNonQuery(object state) { + TestCommandCancelParams cancelCommandTestParamsObject = state as TestCommandCancelParams; + SqlCommand sqlCommand = cancelCommandTestParamsObject?.SqlCommand; + Assert.True(cancelCommandTestParamsObject != null, @"cancelCommandTestParamsObject should not be null."); - SqlCommand sqlCommand = ((TestCommandCancelParams)cancelCommandTestParamsObject).SqlCommand as SqlCommand; Assert.True(sqlCommand != null, "sqlCommand should not be null."); - string.Format(@"UPDATE {0} SET FirstName = @FirstName WHERE FirstName = @FirstName AND CustomerId = @CustomerId", ((TestCommandCancelParams)cancelCommandTestParamsObject).TableName); + try + { + // Wait for the cancellation thread to open this lock... + cancelCommandTestParamsObject.StartWorkloadSignal.Wait(); + + Exception ex = Assert.ThrowsAny(() => sqlCommand.ExecuteNonQuery()); - Exception ex = Assert.Throws(() => sqlCommand.ExecuteNonQuery()); - Assert.Equal(@"Operation cancelled by user.", ex.Message); + // We don't use Assert.Contains() here because it truncates the + // actual and expected strings when outputting a failure. + if (!_cancellationExceptionMessages.Contains(ex.Message)) + { + Assert.Fail( + $"Exception message \"{ex.Message}\" not found in: [\"" + + string.Join("\", \"", _cancellationExceptionMessages) + "\"]"); + } + } + finally + { + // ...and unlock the cancellation thread once we finish. + cancelCommandTestParamsObject.WorkloadCompleteSignal.Set(); + } } - private void Thread_Cancel(object cancelCommandTestParamsObject) + private void Thread_Cancel(object state) { + TestCommandCancelParams cancelCommandTestParamsObject = state as TestCommandCancelParams; + SqlCommand sqlCommand = cancelCommandTestParamsObject?.SqlCommand; + int cancellations = 0; + System.Diagnostics.Stopwatch cancellationStart = new System.Diagnostics.Stopwatch(); + Assert.True(cancelCommandTestParamsObject != null, @"cancelCommandTestParamsObject should not be null."); - SqlCommand sqlCommand = ((TestCommandCancelParams)cancelCommandTestParamsObject).SqlCommand as SqlCommand; Assert.True(sqlCommand != null, "sqlCommand should not be null."); - Thread.Sleep(millisecondsTimeout: 500); + cancellationStart.Start(); + cancelCommandTestParamsObject.StartWorkloadSignal.Set(); - // Repeatedly cancel. - for (int i = 0; i < ((TestCommandCancelParams)cancelCommandTestParamsObject).NumberofTimesToRunCancel; i++) + // Repeatedly cancel until the other thread signals that it's completed execution + // (or until the command timeout has passed.) + do { sqlCommand.Cancel(); - } + cancellations++; + } while (!cancelCommandTestParamsObject.WorkloadCompleteSignal.Wait(0) + && cancellationStart.ElapsedMilliseconds <= sqlCommand.CommandTimeout); + cancellationStart.Stop(); } public void Dispose() @@ -3264,67 +3403,42 @@ internal TestAsyncCallBackStateObject(SqlCommand sqlCommand, int expectedRowsAff internal class TestCommandCancelParams { /// - /// SqlCommand object. - /// - private readonly object _sqlCommand; - - /// - /// Name of the test/works as table name - /// - private readonly string _tableName; - - /// - /// number of times to run cancel. + /// Return the SqlCommand object. /// - private readonly int _numberofCancelCommands; + public SqlCommand SqlCommand { get; } /// - /// Return the SqlCommand object. + /// Return the table name (usually equal to the name of the test.) /// - public object SqlCommand - { - get - { - return _sqlCommand; - } - } + public string TableName { get; } /// - /// Return the tablename. + /// Lock set by the thread cancelling the SqlCommand. /// - public object TableName - { - get - { - return _tableName; - } - } + public ManualResetEventSlim StartWorkloadSignal { get; } /// - /// Return the number of times to run cancel. + /// Lock set by the thread executing the SqlCommand. /// - public int NumberofTimesToRunCancel - { - get - { - return _numberofCancelCommands; - } - } + public ManualResetEventSlim WorkloadCompleteSignal { get; } /// /// Constructor. /// /// /// - /// - public TestCommandCancelParams(object sqlCommand, string tableName, int numberofTimesToCancel) + /// + /// + /// + public TestCommandCancelParams(SqlCommand sqlCommand, string tableName, ManualResetEventSlim startWorkloadSignal, ManualResetEventSlim workloadCompleteSignal) { Assert.True(sqlCommand != null, "sqlCommand should not be null."); Assert.True(!string.IsNullOrWhiteSpace(tableName), "tableName should not be null or empty."); - _sqlCommand = sqlCommand; - _tableName = tableName; - _numberofCancelCommands = numberofTimesToCancel; + SqlCommand = sqlCommand; + TableName = tableName; + StartWorkloadSignal = startWorkloadSignal; + WorkloadCompleteSignal = workloadCompleteSignal; } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs index 94f137dbe9..216db7e0c3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ConversionTests.cs @@ -14,11 +14,12 @@ using System.Security.Cryptography.X509Certificates; using Xunit; using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { [PlatformSpecific(TestPlatforms.Windows)] - public sealed class ConversionTests : IDisposable + public sealed class ConversionTests : IDisposable, IClassFixture { private const string IdentityColumnName = "IdentityColumn"; @@ -29,8 +30,6 @@ public sealed class ConversionTests : IDisposable private const decimal SmallMoneyMinValue = -214748.3648M; private const int MaxLength = 10000; private int NumberOfRows = DataTestUtility.EnclaveEnabled ? 10 : 100; - private static X509Certificate2 certificate; - private ColumnMasterKey columnMasterKey; private ColumnEncryptionKey columnEncryptionKey; private SqlColumnEncryptionCertificateStoreProvider certStoreProvider = new SqlColumnEncryptionCertificateStoreProvider(); private List _databaseObjects = new List(); @@ -54,18 +53,20 @@ public ColumnMetaData(SqlDbType columnType, int columnSize, int precision, int s public bool UseMax { get; set; } } - public ConversionTests() + public ConversionTests(ColumnMasterKeyCertificateFixture fixture) { - if (certificate == null) - { - certificate = CertificateUtility.CreateCertificate(); - } - columnMasterKey = new CspColumnMasterKey(DatabaseHelper.GenerateUniqueName("CMK"), certificate.Thumbprint, certStoreProvider, DataTestUtility.EnclaveEnabled); - _databaseObjects.Add(columnMasterKey); - - columnEncryptionKey = new ColumnEncryptionKey(DatabaseHelper.GenerateUniqueName("CEK"), - columnMasterKey, - certStoreProvider); + X509Certificate2 certificate = fixture.ColumnMasterKeyCertificate; + ColumnMasterKey columnMasterKey1 = new CspColumnMasterKey( + DatabaseHelper.GenerateUniqueName("CMK"), + certificate.Thumbprint, + certStoreProvider, + DataTestUtility.EnclaveEnabled); + _databaseObjects.Add(columnMasterKey1); + + columnEncryptionKey = new ColumnEncryptionKey( + DatabaseHelper.GenerateUniqueName("CEK"), + columnMasterKey1, + certStoreProvider); _databaseObjects.Add(columnEncryptionKey); foreach (string connectionStr in DataTestUtility.AEConnStringsSetup) @@ -82,7 +83,16 @@ public ConversionTests() } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] - [ClassData(typeof(ConversionSmallerToLargerInsertAndSelectData))] + [MemberData( + nameof(ConversionSmallerToLargerInsertAndSelectData) +#if NETFRAMEWORK + // .NET Framework puts system enums in something called the Global + // Assembly Cache (GAC), and xUnit refuses to serialize enums that + // live there. So for .NET Framework, we disable enumeration of the + // test data to avoid warnings on the console when running tests. + , DisableDiscoveryEnumeration = true +#endif + )] public void ConversionSmallerToLargerInsertAndSelect(string connString, SqlDbType smallDbType, SqlDbType largeDbType) { ColumnMetaData largeColumnInfo = new ColumnMetaData(largeDbType, 0, 1, 1, false); @@ -162,7 +172,12 @@ public void ConversionSmallerToLargerInsertAndSelect(string connString, SqlDbTyp } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] - [ClassData(typeof(ConversionSmallerToLargerInsertAndSelectBulkData))] + [MemberData( + nameof(ConversionSmallerToLargerInsertAndSelectBulkData) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void ConversionSmallerToLargerInsertAndSelectBulk(string connString, SqlDbType smallDbType, SqlDbType largeDbType) { ColumnMetaData largeColumnInfo = new ColumnMetaData(largeDbType, 0, 1, 1, false); @@ -251,7 +266,12 @@ smallColumnInfo.ColumnType is SqlDbType.Char || } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] - [ClassData(typeof(TestOutOfRangeValuesData))] + [MemberData( + nameof(TestOutOfRangeValuesData) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public void TestOutOfRangeValues(string connString, SqlDbType currentDbType) { ColumnMetaData currentColumnInfo = new ColumnMetaData(currentDbType, 0, 1, 1, false); @@ -565,7 +585,20 @@ private void ExecuteAndCheckForError(SqlCommand sqlCmd, bool expectError) try { sqlCmd.ExecuteNonQuery(); - Assert.Fail("We should have gotten an error but passed instead."); + StringBuilder builder = new( + "We should have gotten an error but passed instead; " + + $"command: {sqlCmd.CommandText}; parameters: "); + foreach (SqlParameter param in sqlCmd.Parameters) + { + builder.Append('('); + builder.Append(param.ParameterName); + builder.Append(' '); + builder.Append(param.SqlDbType); + builder.Append(' '); + builder.Append(param.Value); + builder.Append(") "); + } + Assert.Fail(builder.ToString()); } catch (Exception e) { @@ -1336,11 +1369,8 @@ public void Dispose() } } } - } - public class ConversionSmallerToLargerInsertAndSelectData : IEnumerable - { - public IEnumerator GetEnumerator() + public static IEnumerable ConversionSmallerToLargerInsertAndSelectData() { foreach (string connStrAE in DataTestUtility.AEConnStrings) { @@ -1374,12 +1404,8 @@ public IEnumerator GetEnumerator() yield return new object[] { connStrAE, SqlDbType.Real, SqlDbType.Real }; } } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - public class ConversionSmallerToLargerInsertAndSelectBulkData : IEnumerable - { - public IEnumerator GetEnumerator() + public static IEnumerable ConversionSmallerToLargerInsertAndSelectBulkData() { foreach (string connStrAE in DataTestUtility.AEConnStrings) { @@ -1414,13 +1440,7 @@ public IEnumerator GetEnumerator() } } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - - - public class TestOutOfRangeValuesData : IEnumerable - { - public IEnumerator GetEnumerator() + public static IEnumerable TestOutOfRangeValuesData() { foreach (string connStrAE in DataTestUtility.AEConnStrings) { @@ -1449,7 +1469,5 @@ public IEnumerator GetEnumerator() yield return new object[] { connStrAE, SqlDbType.VarChar }; } } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs index f164dff220..bdd48967b5 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs @@ -2,14 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; +using System.Collections.Generic; +using System.IO; using System.Linq; using System.Security.Cryptography.X509Certificates; -using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; using Xunit; -using System.Collections.Generic; -using static Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.CertificateUtilityWin; -#if !NETFRAMEWORK +using System.Security.Cryptography; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures; +using Microsoft.Win32; + +#if NET using System.Runtime.Versioning; #endif @@ -25,185 +27,97 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted [PlatformSpecific(TestPlatforms.Windows)] public class CspProviderExt { - [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] - [ClassData(typeof(AEConnectionStringProvider))] - public void TestKeysFromCertificatesCreatedWithMultipleCryptoProviders(string connectionString) - { - const string providersRegistryKeyPath = @"SOFTWARE\Microsoft\Cryptography\Defaults\Provider"; - Microsoft.Win32.RegistryKey defaultCryptoProvidersRegistryKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(providersRegistryKeyPath); - - foreach (string subKeyName in defaultCryptoProvidersRegistryKey.GetSubKeyNames()) - { - // NOTE: RSACryptoServiceProvider.SignData() fails for other providers when testing locally - if (!subKeyName.Contains(@"RSA and AES")) - { - Console.WriteLine(@"INFO: Skipping Certificate creation for {0}.", subKeyName); - continue; - } - string providerName; - string providerType; - string certificateName; - using (Microsoft.Win32.RegistryKey providerKey = defaultCryptoProvidersRegistryKey.OpenSubKey(subKeyName)) - { - // Get Provider Name and its type - providerName = providerKey.Name.Substring(providerKey.Name.LastIndexOf(@"\", StringComparison.Ordinal) + 1); - providerType = providerKey.GetValue(@"Type").ToString(); - - // Create a certificate from that provider - certificateName = string.Format(@"AETest - {0}", providerName); - } - - var extensions = new List>(); - CertificateOption certOption = new() - { - Subject = certificateName, - KeyExportPolicy = "Exportable", - CertificateStoreLocation = $"{StoreLocation.CurrentUser}\\My", - Provider = providerName, - Type = providerType, - }; - CreateCertificate(certOption); - SQLSetupStrategyCspExt sqlSetupStrategyCsp = null; - try - { - if (false == CertificateUtilityWin.CertificateExists(certificateName, StoreLocation.CurrentUser)) - { - Console.WriteLine(@"INFO: Certificate creation for provider {0} failed so skipping it.", providerName); - continue; - } - - X509Certificate2 cert = CertificateUtilityWin.GetCertificate(certificateName, StoreLocation.CurrentUser); - string cspPath = CertificateUtilityWin.GetCspPathFromCertificate(cert); - - if (string.IsNullOrEmpty(cspPath)) - { - Console.WriteLine(@"INFO: Certificate provider {0} is not a csp provider so skipping it.", providerName); - continue; - } - - Console.WriteLine("CSP path is {0}", cspPath); - - sqlSetupStrategyCsp = new SQLSetupStrategyCspExt(cspPath); - string tableName = sqlSetupStrategyCsp.CspProviderTable.Name; - - using SqlConnection sqlConn = new(connectionString); - sqlConn.Open(); - - Table.DeleteData(tableName, sqlConn); - - // insert 1 row data - Customer customer = new Customer(45, "Microsoft", "Corporation"); - - DatabaseHelper.InsertCustomerData(sqlConn, null, tableName, customer); - - // Test INPUT parameter on an encrypted parameter - using SqlCommand sqlCommand = new(@$"SELECT CustomerId, FirstName, LastName FROM [{tableName}] WHERE FirstName = @firstName", - sqlConn, null, SqlCommandColumnEncryptionSetting.Enabled); - SqlParameter customerFirstParam = sqlCommand.Parameters.AddWithValue(@"firstName", @"Microsoft"); - customerFirstParam.Direction = System.Data.ParameterDirection.Input; - - using SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(); - ValidateResultSet(sqlDataReader); - Console.WriteLine(@"INFO: Successfully validated using a certificate using provider:{0}", providerName); - } - finally - { - CertificateUtilityWin.RemoveCertificate(certificateName, StoreLocation.CurrentUser); - // clean up database resources - sqlSetupStrategyCsp?.Dispose(); - } - - } - } - // [Fact(Skip="Run this in non-parallel mode")] or [ConditionalFact()] [Fact(Skip = "Failing in TCE")] - public void TestRoundTripWithCSPAndCertStoreProvider() + public void TestRoundTripWithCspAndCertStoreProvider() { - const string providerName = "Microsoft Enhanced RSA and AES Cryptographic Provider"; - string providerType = "24"; + using CspCertificateFixture cspCertificateFixture = new CspCertificateFixture(); - string certificateName = string.Format(@"AETest - {0}", providerName); - CertificateOption options = new() - { - Subject = certificateName, - CertificateStoreLocation = StoreLocation.CurrentUser.ToString(), - Provider = providerName, - Type = providerType - }; - CertificateUtilityWin.CreateCertificate(options); - try - { - X509Certificate2 cert = CertificateUtilityWin.GetCertificate(certificateName, StoreLocation.CurrentUser); - string cspPath = CertificateUtilityWin.GetCspPathFromCertificate(cert); - string certificatePath = String.Concat(@"CurrentUser/my/", cert.Thumbprint); + X509Certificate2 cert = cspCertificateFixture.CspCertificate; + string cspPath = cspCertificateFixture.CspKeyPath; + string certificatePath = cspCertificateFixture.CspCertificatePath; - SqlColumnEncryptionCertificateStoreProvider certProvider = new SqlColumnEncryptionCertificateStoreProvider(); - SqlColumnEncryptionCspProvider cspProvider = new SqlColumnEncryptionCspProvider(); - byte[] columnEncryptionKey = DatabaseHelper.GenerateRandomBytes(32); + SqlColumnEncryptionCertificateStoreProvider certProvider = new SqlColumnEncryptionCertificateStoreProvider(); + SqlColumnEncryptionCspProvider cspProvider = new SqlColumnEncryptionCspProvider(); + byte[] columnEncryptionKey = DatabaseHelper.GenerateRandomBytes(32); - byte[] encryptedColumnEncryptionKeyUsingCert = certProvider.EncryptColumnEncryptionKey(certificatePath, @"RSA_OAEP", columnEncryptionKey); - byte[] columnEncryptionKeyReturnedCert2CSP = cspProvider.DecryptColumnEncryptionKey(cspPath, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingCert); - Assert.True(columnEncryptionKey.SequenceEqual(columnEncryptionKeyReturnedCert2CSP)); + byte[] encryptedColumnEncryptionKeyUsingCert = certProvider.EncryptColumnEncryptionKey(certificatePath, @"RSA_OAEP", columnEncryptionKey); + byte[] columnEncryptionKeyReturnedCert2CSP = cspProvider.DecryptColumnEncryptionKey(cspPath, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingCert); + Assert.True(columnEncryptionKey.SequenceEqual(columnEncryptionKeyReturnedCert2CSP)); - byte[] encryptedColumnEncryptionKeyUsingCSP = cspProvider.EncryptColumnEncryptionKey(cspPath, @"RSA_OAEP", columnEncryptionKey); - byte[] columnEncryptionKeyReturnedCSP2Cert = certProvider.DecryptColumnEncryptionKey(certificatePath, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingCSP); - Assert.True(columnEncryptionKey.SequenceEqual(columnEncryptionKeyReturnedCSP2Cert)); - - } - finally - { - CertificateUtilityWin.RemoveCertificate(certificateName, StoreLocation.CurrentUser); - } + byte[] encryptedColumnEncryptionKeyUsingCSP = cspProvider.EncryptColumnEncryptionKey(cspPath, @"RSA_OAEP", columnEncryptionKey); + byte[] columnEncryptionKeyReturnedCSP2Cert = certProvider.DecryptColumnEncryptionKey(certificatePath, @"RSA_OAEP", encryptedColumnEncryptionKeyUsingCSP); + Assert.True(columnEncryptionKey.SequenceEqual(columnEncryptionKeyReturnedCSP2Cert)); } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringSetupForAE))] - [ClassData(typeof(AEConnectionStringProvider))] - public void TestEncryptDecryptWithCSP(string connectionString) + [MemberData(nameof(TestEncryptDecryptWithCsp_Data))] + public void TestEncryptDecryptWithCsp(string connectionString, string providerName, int providerType) { - string providerName = @"Microsoft Enhanced RSA and AES Cryptographic Provider"; string keyIdentifier = DataTestUtility.GetUniqueNameForSqlServer("CSP"); + CspParameters namedCspParameters = new CspParameters(providerType, providerName, keyIdentifier); + using SQLSetupStrategyCspProvider sqlSetupStrategyCsp = new SQLSetupStrategyCspProvider(namedCspParameters); + + using SqlConnection sqlConn = new(connectionString); + sqlConn.Open(); + + // Test INPUT parameter on an encrypted parameter + string commandText = @$"SELECT CustomerId, FirstName, LastName " + + @$"FROM [{sqlSetupStrategyCsp.ApiTestTable.Name}] " + + @$"WHERE FirstName = @firstName"; + using SqlCommand sqlCommand = new(commandText, sqlConn, null, SqlCommandColumnEncryptionSetting.Enabled); + + SqlParameter customerFirstParam = sqlCommand.Parameters.AddWithValue(@"firstName", @"Microsoft"); + customerFirstParam.Direction = System.Data.ParameterDirection.Input; + + using SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(); + ValidateResultSet(sqlDataReader); + } - try + public static IEnumerable TestEncryptDecryptWithCsp_Data + { + get { - CertificateUtilityWin.RSAPersistKeyInCsp(providerName, keyIdentifier); - string cspPath = String.Concat(providerName, @"/", keyIdentifier); - - SQLSetupStrategyCspExt sqlSetupStrategyCsp = new SQLSetupStrategyCspExt(cspPath); - string tableName = sqlSetupStrategyCsp.CspProviderTable.Name; - - try + const string providerRegistryKeyPath = @"SOFTWARE\Microsoft\Cryptography\Defaults\Provider"; + using RegistryKey defaultProviderRegistryKey = Registry.LocalMachine.OpenSubKey(providerRegistryKeyPath); + if (defaultProviderRegistryKey is null) { - using SqlConnection sqlConn = new(connectionString); - sqlConn.Open(); - - Table.DeleteData(tableName, sqlConn); - - // insert 1 row data - Customer customer = new Customer(45, "Microsoft", "Corporation"); - - DatabaseHelper.InsertCustomerData(sqlConn, null, tableName, customer); - - // Test INPUT parameter on an encrypted parameter - using SqlCommand sqlCommand = new(@$"SELECT CustomerId, FirstName, LastName FROM [{tableName}] WHERE FirstName = @firstName", - sqlConn, null, SqlCommandColumnEncryptionSetting.Enabled); - SqlParameter customerFirstParam = sqlCommand.Parameters.AddWithValue(@"firstName", @"Microsoft"); - Console.WriteLine(@"Exception: {0}"); - customerFirstParam.Direction = System.Data.ParameterDirection.Input; - - using SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(); - ValidateResultSet(sqlDataReader); + // No test cases can be generated if the registry key doesn't exist. + yield break; } - finally + + foreach (string subKeyName in defaultProviderRegistryKey.GetSubKeyNames()) { - // clean up database resources - sqlSetupStrategyCsp.Dispose(); + // Skip inappropriate providers + if (!subKeyName.Contains(@"RSA and AES")) + { + continue; + } + + // Open the provider + using RegistryKey providerKey = defaultProviderRegistryKey.OpenSubKey(subKeyName); + if (providerKey is null) + { + continue; + } + + // Read provider name + string providerName = Path.GetFileName(providerKey.Name); + + // Read provider type + object providerTypeValue = providerKey.GetValue(@"Type"); + if (providerTypeValue is not int providerType) + { + continue; + } + + // Combine with AE connection strings + foreach (string aeConnectionString in DataTestUtility.AEConnStrings) + { + yield return new object[] { aeConnectionString, providerName, providerType }; + } } } - finally - { - CertificateUtilityWin.RSADeleteKeyInCsp(providerName, keyIdentifier); - } } /// diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/EnclaveAzureDatabaseTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/EnclaveAzureDatabaseTests.cs index c372e39bca..536be83a3e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/EnclaveAzureDatabaseTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/EnclaveAzureDatabaseTests.cs @@ -14,7 +14,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { // This test class is for internal use only - public sealed class EnclaveAzureDatabaseTests : IDisposable + public sealed class EnclaveAzureDatabaseTests : IDisposable, IClassFixture { private ColumnMasterKey akvColumnMasterKey; private ColumnEncryptionKey akvColumnEncryptionKey; @@ -22,7 +22,7 @@ public sealed class EnclaveAzureDatabaseTests : IDisposable private List databaseObjects = new List(); private List connStrings = new List(); - public EnclaveAzureDatabaseTests() + public EnclaveAzureDatabaseTests(AzureKeyVaultKeyFixture keyVaultKeyFixture) { if (DataTestUtility.IsEnclaveAzureDatabaseSetup()) { @@ -32,7 +32,7 @@ public EnclaveAzureDatabaseTests() SQLSetupStrategyAzureKeyVault.RegisterGlobalProviders(sqlColumnEncryptionAzureKeyVaultProvider); } - akvColumnMasterKey = new AkvColumnMasterKey(DatabaseHelper.GenerateUniqueName("AKVCMK"), akvUrl: DataTestUtility.AKVUrl, sqlColumnEncryptionAzureKeyVaultProvider, DataTestUtility.EnclaveEnabled); + akvColumnMasterKey = new AkvColumnMasterKey(DatabaseHelper.GenerateUniqueName("AKVCMK"), akvUrl: keyVaultKeyFixture.GeneratedKeyUri, sqlColumnEncryptionAzureKeyVaultProvider, DataTestUtility.EnclaveEnabled); databaseObjects.Add(akvColumnMasterKey); akvColumnEncryptionKey = new ColumnEncryptionKey(DatabaseHelper.GenerateUniqueName("AKVCEK"), diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs index 6cb20a4351..15679176ac 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionTestAKVStore.cs @@ -15,16 +15,16 @@ public class ExceptionTestAKVStore : IClassFixture(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, BadMasterKeyEncAlgo, cek)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, BadMasterKeyEncAlgo, cek)); Assert.Matches($@"Invalid key encryption algorithm specified: 'BadMasterKeyAlgorithm'. Expected value: 'RSA_OAEP' or 'RSA-OAEP'.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?", ex1.Message); - Exception ex2 = Assert.Throws(() => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, BadMasterKeyEncAlgo, cek)); + Exception ex2 = Assert.Throws(() => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(_fixture.AkvKeyUrl, BadMasterKeyEncAlgo, cek)); Assert.Matches($@"Invalid key encryption algorithm specified: 'BadMasterKeyAlgorithm'. Expected value: 'RSA_OAEP' or 'RSA-OAEP'.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?", ex2.Message); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] public void NullEncryptionAlgorithm() { - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, null, cek)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, null, cek)); Assert.Matches($@"Internal error. Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?", ex1.Message); - Exception ex2 = Assert.Throws(() => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, null, cek)); + Exception ex2 = Assert.Throws(() => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(_fixture.AkvKeyUrl, null, cek)); Assert.Matches($@"Internal error. Key encryption algorithm cannot be null.\s+\(?Parameter (name: )?'?encryptionAlgorithm('\))?", ex2.Message); } @@ -53,28 +53,28 @@ public void NullEncryptionAlgorithm() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] public void EmptyColumnEncryptionKey() { - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, new byte[] { })); - Assert.Matches($@"Internal error. Empty columnEncryptionKey specified.", ex1.Message); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, new byte[] { })); + Assert.Matches($@"Internal error. Empty 'columnEncryptionKey' specified.", ex1.Message); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] public void NullColumnEncryptionKey() { - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, null)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, null)); Assert.Matches($@"Value cannot be null.\s+\(?Parameter (name: )?'?columnEncryptionKey('\))?", ex1.Message); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] public void EmptyEncryptedColumnEncryptionKey() { - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, new byte[] { })); - Assert.Matches($@"Internal error. Empty encryptedColumnEncryptionKey specified", ex1.Message); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, new byte[] { })); + Assert.Matches($@"Internal error. Empty 'encryptedColumnEncryptionKey' specified", ex1.Message); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsAKVSetupAvailable))] public void NullEncryptedColumnEncryptionKey() { - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, null)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, null)); Assert.Matches($@"Value cannot be null.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?", ex1.Message); } @@ -82,10 +82,10 @@ public void NullEncryptedColumnEncryptionKey() public void InvalidAlgorithmVersion() { byte[] encrypteCekLocal = ColumnEncryptionKey.GenerateInvalidEncryptedCek(encryptedCek, ColumnEncryptionKey.ECEKCorruption.ALGORITHM_VERSION); - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, encrypteCekLocal)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, encrypteCekLocal)); Assert.Matches($@"Specified encrypted column encryption key contains an invalid encryption algorithm version '10'. Expected version is '01'.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?", ex1.Message); - Exception ex2 = Assert.Throws(() => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(DataTestUtility.AKVUrl, "RSA_CORRUPT", encryptedCek)); + Exception ex2 = Assert.Throws(() => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(_fixture.AkvKeyUrl, "RSA_CORRUPT", encryptedCek)); Assert.Contains("Invalid key encryption algorithm specified: 'RSA_CORRUPT'. Expected value: 'RSA_OAEP' or 'RSA-OAEP'.", ex2.Message); } @@ -95,9 +95,9 @@ public void InvalidCertificateSignature() // Put an invalid signature byte[] encrypteCekLocal = ColumnEncryptionKey.GenerateInvalidEncryptedCek(encryptedCek, ColumnEncryptionKey.ECEKCorruption.SIGNATURE); string errorMessage = - $@"The specified encrypted column encryption key signature does not match the signature computed with the column master key \(Asymmetric key in Azure Key Vault\) in '{DataTestUtility.AKVUrl}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + $@"The specified encrypted column encryption key signature does not match the signature computed with the column master key \(Asymmetric key in Azure Key Vault\) in '{_fixture.AkvKeyUrl}'. The encrypted column encryption key may be corrupt, or the specified path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, encrypteCekLocal)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, encrypteCekLocal)); Assert.Matches(errorMessage, ex1.Message); } @@ -106,9 +106,9 @@ public void InvalidCipherTextLength() { // Put an invalid signature byte[] encrypteCekLocal = ColumnEncryptionKey.GenerateInvalidEncryptedCek(encryptedCek, ColumnEncryptionKey.ECEKCorruption.CEK_LENGTH); - string errorMessage = $@"The specified encrypted column encryption key's ciphertext length: 251 does not match the ciphertext length: 256 when using column master key \(Azure Key Vault key\) in '{DataTestUtility.AKVUrl}'. The encrypted column encryption key may be corrupt, or the specified Azure Key Vault key path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + string errorMessage = $@"The specified encrypted column encryption key's ciphertext length: 251 does not match the ciphertext length: 256 when using column master key \(Azure Key Vault key\) in '{_fixture.AkvKeyUrl}'. The encrypted column encryption key may be corrupt, or the specified Azure Key Vault key path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, encrypteCekLocal)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, encrypteCekLocal)); Assert.Matches(errorMessage, ex1.Message); } @@ -116,9 +116,9 @@ public void InvalidCipherTextLength() public void InvalidSignatureInEncryptedCek() { byte[] encryptedCekLocal = ColumnEncryptionKey.GenerateInvalidEncryptedCek(encryptedCek, ColumnEncryptionKey.ECEKCorruption.SIGNATURE_LENGTH); - string errorMessage = $@"The specified encrypted column encryption key's signature length: 249 does not match the signature length: 256 when using column master key \(Azure Key Vault key\) in '{DataTestUtility.AKVUrl}'. The encrypted column encryption key may be corrupt, or the specified Azure Key Vault key path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; + string errorMessage = $@"The specified encrypted column encryption key's signature length: 249 does not match the signature length: 256 when using column master key \(Azure Key Vault key\) in '{_fixture.AkvKeyUrl}'. The encrypted column encryption key may be corrupt, or the specified Azure Key Vault key path may be incorrect.\s+\(?Parameter (name: )?'?encryptedColumnEncryptionKey('\))?"; - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(DataTestUtility.AKVUrl, MasterKeyEncAlgo, encryptedCekLocal)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(_fixture.AkvKeyUrl, MasterKeyEncAlgo, encryptedCekLocal)); Assert.Matches(errorMessage, ex1.Message); } @@ -134,10 +134,10 @@ public void InvalidURL() string fakePath = new string(barePath); string errorMessage = $@"Invalid url specified: '{fakePath}'.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; - Exception ex1 = Assert.Throws(() => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(fakePath, MasterKeyEncAlgo, cek)); + Exception ex1 = Assert.Throws(() => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(fakePath, MasterKeyEncAlgo, cek)); Assert.Matches(errorMessage, ex1.Message); - Exception ex2 = Assert.Throws(() => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(fakePath, MasterKeyEncAlgo, encryptedCek)); + Exception ex2 = Assert.Throws(() => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(fakePath, MasterKeyEncAlgo, encryptedCek)); Assert.Matches(errorMessage, ex2.Message); } @@ -145,11 +145,11 @@ public void InvalidURL() public void NullAKVKeyPath() { Exception ex1 = Assert.Throws( - () => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(null, MasterKeyEncAlgo, cek)); + () => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(null, MasterKeyEncAlgo, cek)); Assert.Matches($@"Azure Key Vault key path cannot be null.\s+\(?Parameter (name: )?'?masterKeyPath('\))?", ex1.Message); Exception ex2 = Assert.Throws( - () => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(null, MasterKeyEncAlgo, encryptedCek)); + () => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(null, MasterKeyEncAlgo, encryptedCek)); Assert.Matches($@"Internal error. Azure Key Vault key path cannot be null.\s+\(?Parameter (name: )?'?masterKeyPath('\))?", ex2.Message); } @@ -185,19 +185,19 @@ public void InvalidCertificatePath() string invalidTrustedEndpointErrorMessage = $@"Invalid Azure Key Vault key path specified: '{dummyPathWithInvalidKey}'. Valid trusted endpoints: vault.azure.net, vault.azure.cn, vault.usgovcloudapi.net, vault.microsoftazure.de, managedhsm.azure.net, managedhsm.azure.cn, managedhsm.usgovcloudapi.net, managedhsm.microsoftazure.de.\s+\(?Parameter (name: )?'?masterKeyPath('\))?"; Exception ex = Assert.Throws( - () => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(dummyPathWithOnlyHost, MasterKeyEncAlgo, cek)); + () => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(dummyPathWithOnlyHost, MasterKeyEncAlgo, cek)); Assert.Matches(invalidUrlErrorMessage, ex.Message); ex = Assert.Throws( - () => fixture.AkvStoreProvider.EncryptColumnEncryptionKey(dummyPathWithInvalidKey, MasterKeyEncAlgo, cek)); + () => _fixture.AkvStoreProvider.EncryptColumnEncryptionKey(dummyPathWithInvalidKey, MasterKeyEncAlgo, cek)); Assert.Matches(invalidTrustedEndpointErrorMessage, ex.Message); ex = Assert.Throws( - () => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(dummyPathWithOnlyHost, MasterKeyEncAlgo, encryptedCek)); + () => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(dummyPathWithOnlyHost, MasterKeyEncAlgo, encryptedCek)); Assert.Matches(invalidUrlErrorMessage, ex.Message); ex = Assert.Throws( - () => fixture.AkvStoreProvider.DecryptColumnEncryptionKey(dummyPathWithInvalidKey, MasterKeyEncAlgo, encryptedCek)); + () => _fixture.AkvStoreProvider.DecryptColumnEncryptionKey(dummyPathWithInvalidKey, MasterKeyEncAlgo, encryptedCek)); Assert.Matches(invalidTrustedEndpointErrorMessage, ex.Message); } @@ -207,11 +207,11 @@ public void InvalidCertificatePath() public void AkvStoreProviderVerifyFunctionWithInvalidSignature(bool fEnclaveEnabled) { //sign the cmk - byte[] cmkSignature = fixture.AkvStoreProvider.SignColumnMasterKeyMetadata(DataTestUtility.AKVUrl, allowEnclaveComputations: fEnclaveEnabled); + byte[] cmkSignature = _fixture.AkvStoreProvider.SignColumnMasterKeyMetadata(_fixture.AkvKeyUrl, allowEnclaveComputations: fEnclaveEnabled); Assert.True(cmkSignature != null); // Expect failed verification for a toggle of enclaveEnabled bit - Assert.False(fixture.AkvStoreProvider.VerifyColumnMasterKeyMetadata(DataTestUtility.AKVUrl, allowEnclaveComputations: !fEnclaveEnabled, signature: cmkSignature)); + Assert.False(_fixture.AkvStoreProvider.VerifyColumnMasterKeyMetadata(_fixture.AkvKeyUrl, allowEnclaveComputations: !fEnclaveEnabled, signature: cmkSignature)); // Prepare another cipherText buffer byte[] tamperedCmkSignature = new byte[cmkSignature.Length]; @@ -223,7 +223,7 @@ public void AkvStoreProviderVerifyFunctionWithInvalidSignature(bool fEnclaveEnab byte[] randomIndexInCipherText = new byte[1]; for (int i = 0; i < 10; i++) { - Assert.True(fixture.AkvStoreProvider.VerifyColumnMasterKeyMetadata(DataTestUtility.AKVUrl, allowEnclaveComputations: fEnclaveEnabled, signature: tamperedCmkSignature), @"tamperedCmkSignature before tampering should be verified without any problems."); + Assert.True(_fixture.AkvStoreProvider.VerifyColumnMasterKeyMetadata(_fixture.AkvKeyUrl, allowEnclaveComputations: fEnclaveEnabled, signature: tamperedCmkSignature), @"tamperedCmkSignature before tampering should be verified without any problems."); int startingByteIndex = 0; rng.GetBytes(randomIndexInCipherText); @@ -231,7 +231,7 @@ public void AkvStoreProviderVerifyFunctionWithInvalidSignature(bool fEnclaveEnab tamperedCmkSignature[startingByteIndex + randomIndexInCipherText[0]] = (byte)(cmkSignature[startingByteIndex + randomIndexInCipherText[0]] + 1); // Expect failed verification for invalid signature bytes - Assert.False(fixture.AkvStoreProvider.VerifyColumnMasterKeyMetadata(DataTestUtility.AKVUrl, allowEnclaveComputations: fEnclaveEnabled, signature: tamperedCmkSignature)); + Assert.False(_fixture.AkvStoreProvider.VerifyColumnMasterKeyMetadata(_fixture.AkvKeyUrl, allowEnclaveComputations: fEnclaveEnabled, signature: tamperedCmkSignature)); // Fix up the corrupted byte tamperedCmkSignature[startingByteIndex + randomIndexInCipherText[0]] = cmkSignature[startingByteIndex + randomIndexInCipherText[0]]; @@ -250,7 +250,7 @@ public void InvalidTrustedEndpoints(string[] trustedEndpoints) SqlColumnEncryptionAzureKeyVaultProvider azureKeyProvider = new SqlColumnEncryptionAzureKeyVaultProvider( new SqlClientCustomTokenCredential(), trustedEndpoints); }); - Assert.Matches("One or more of the elements in trustedEndpoints are null or empty or consist of only whitespace.", ex.Message); + Assert.Matches("One or more of the elements in 'trustedEndpoints' are null or empty or consist of only whitespace.", ex.Message); } [InlineData(null)] @@ -264,7 +264,7 @@ public void InvalidTrustedEndpoint(string trustedEndpoint) SqlColumnEncryptionAzureKeyVaultProvider azureKeyProvider = new SqlColumnEncryptionAzureKeyVaultProvider( new SqlClientCustomTokenCredential(), trustedEndpoint); }); - Assert.Matches("One or more of the elements in trustedEndpoints are null or empty or consist of only whitespace.", ex.Message); + Assert.Matches("One or more of the elements in 'trustedEndpoints' are null or empty or consist of only whitespace.", ex.Message); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.cs new file mode 100644 index 0000000000..b59cab7e3b --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/AzureKeyVaultKeyFixture.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information.using System; + +using Microsoft.Data.SqlClient.Tests.Common.Fixtures; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted +{ + public sealed class AzureKeyVaultKeyFixture : AzureKeyVaultKeyFixtureBase + { + public AzureKeyVaultKeyFixture() + : base(DataTestUtility.AKVBaseUri, DataTestUtility.GetTokenCredential()) + { + GeneratedKeyUri = CreateKey(nameof(GeneratedKeyUri), 2048).ToString(); + } + + public string GeneratedKeyUri { get; } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs index 63cfdab974..9c85c4fc67 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/DatabaseHelper.cs @@ -213,68 +213,59 @@ internal static byte[] GenerateRandomBytes(int length) internal static string GenerateUniqueName(string baseName) => string.Concat("AE_", baseName, "_", Guid.NewGuid().ToString().Replace('-', '_')); } - public class AEConnectionStringProviderWithBooleanVariable : IEnumerable + public static class DataHelpers { - public IEnumerator GetEnumerator() + public static IEnumerable AEConnectionStringProviderWithSchemaType() { foreach (string connStrAE in DataTestUtility.AEConnStrings) { - yield return new object[] { connStrAE, true }; - yield return new object[] { connStrAE, false }; + yield return new object[] { connStrAE, SchemaType.Source }; + yield return new object[] { connStrAE, SchemaType.Mapped }; } } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - - public class AEConnectionStringProvider : IEnumerable - { - public IEnumerator GetEnumerator() + public static IEnumerable AEConnectionStringProviderWithCommandBehaviorSet1() { foreach (string connStrAE in DataTestUtility.AEConnStrings) { - yield return new object[] { connStrAE }; + yield return new object[] { connStrAE, CommandBehavior.SingleResult }; + yield return new object[] { connStrAE, CommandBehavior.SingleRow }; + yield return new object[] { connStrAE, CommandBehavior.CloseConnection }; + yield return new object[] { connStrAE, CommandBehavior.SequentialAccess }; } } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - } - public class AEConnectionStringProviderWithCommandBehaviorSet1 : IEnumerable - { - public IEnumerator GetEnumerator() + public static IEnumerable AEConnectionStringProviderWithCommandBehaviorSet2() { foreach (string connStrAE in DataTestUtility.AEConnStrings) { - yield return new object[] { connStrAE, CommandBehavior.SingleResult }; - yield return new object[] { connStrAE, CommandBehavior.SingleRow }; - yield return new object[] { connStrAE, CommandBehavior.CloseConnection }; + yield return new object[] { connStrAE, CommandBehavior.Default }; yield return new object[] { connStrAE, CommandBehavior.SequentialAccess }; } } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } - public class AEConnectionStringProviderWithCommandBehaviorSet2 : IEnumerable + public class AEConnectionStringProviderWithBooleanVariable : IEnumerable { public IEnumerator GetEnumerator() { foreach (string connStrAE in DataTestUtility.AEConnStrings) { - yield return new object[] { connStrAE, CommandBehavior.Default }; - yield return new object[] { connStrAE, CommandBehavior.SequentialAccess }; + yield return new object[] { connStrAE, true }; + yield return new object[] { connStrAE, false }; } } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } - public class AEConnectionStringProviderWithSchemaType : IEnumerable + public class AEConnectionStringProvider : IEnumerable { public IEnumerator GetEnumerator() { foreach (string connStrAE in DataTestUtility.AEConnStrings) { - yield return new object[] { connStrAE, SchemaType.Source }; - yield return new object[] { connStrAE, SchemaType.Mapped }; + yield return new object[] { connStrAE }; } } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -299,10 +290,8 @@ public IEnumerator GetEnumerator() { foreach (string connStrAE in DataTestUtility.AEConnStrings) { - yield return new object[] { connStrAE, @"ExecuteReader", 1 }; - yield return new object[] { connStrAE, @"ExecuteReader", 3 }; - yield return new object[] { connStrAE, @"ExecuteNonQuery", 1 }; - yield return new object[] { connStrAE, @"ExecuteNonQuery", 3 }; + yield return new object[] { connStrAE, @"ExecuteReader" }; + yield return new object[] { connStrAE, @"ExecuteNonQuery" }; } } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs index 2bc38d9930..d08d2a86be 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs @@ -9,15 +9,15 @@ using System.Security.Cryptography.X509Certificates; using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.TestFixtures.Setup; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { - public class SQLSetupStrategy : IDisposable + public class SQLSetupStrategy : ColumnMasterKeyCertificateFixture { internal const string ColumnEncryptionAlgorithmName = @"AEAD_AES_256_CBC_HMAC_SHA256"; - protected static X509Certificate2 certificate; - public string keyPath { get; internal set; } + public string ColumnMasterKeyPath { get; } public Table ApiTestTable { get; private set; } public Table BulkCopyAEErrorMessageTestTable { get; private set; } public Table BulkCopyAETestTable { get; private set; } @@ -59,15 +59,16 @@ public class SQLSetupStrategy : IDisposable public Dictionary sqlBulkTruncationTableNames = new Dictionary(); public SQLSetupStrategy() + : base(true) { - if (certificate == null) - { - certificate = CertificateUtility.CreateCertificate(); - } - keyPath = string.Concat(StoreLocation.CurrentUser.ToString(), "/", StoreName.My.ToString(), "/", certificate.Thumbprint); + ColumnMasterKeyPath = $"{StoreLocation.CurrentUser}/{StoreName.My}/{ColumnMasterKeyCertificate.Thumbprint}"; } - protected SQLSetupStrategy(string customKeyPath) => keyPath = customKeyPath; + protected SQLSetupStrategy(string customKeyPath) + : base(false) + { + ColumnMasterKeyPath = customKeyPath; + } internal virtual void SetupDatabase() { @@ -88,7 +89,15 @@ internal virtual void SetupDatabase() } } - // Insert data for TrustedMasterKeyPaths tests. + } + // Insert data for TrustedMasterKeyPaths tests. + InsertSampleData(TrustedMasterKeyPathsTestTable.Name); + } + + protected void InsertSampleData(string tableName) + { + foreach(string value in DataTestUtility.AEConnStringsSetup) + { SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(value) { ConnectTimeout = 10000 @@ -97,7 +106,9 @@ internal virtual void SetupDatabase() using (SqlConnection sqlConn = new SqlConnection(builder.ToString())) { sqlConn.Open(); - DatabaseHelper.InsertCustomerData(sqlConn, null, TrustedMasterKeyPathsTestTable.Name, customer); + + Table.DeleteData(tableName, sqlConn); + DatabaseHelper.InsertCustomerData(sqlConn, null, tableName, customer); } } } @@ -146,9 +157,12 @@ protected List CreateTables(IList columnEncryptionKe SqlNullValuesTable = new SqlNullValuesTable(GenerateUniqueName("SqlNullValuesTable"), columnEncryptionKeys[0]); tables.Add(SqlNullValuesTable); - // columnEncryptionKeys[2] is encrypted with DummyCMK. use this encrypted column to test custom key store providers - CustomKeyStoreProviderTestTable = new ApiTestTable(GenerateUniqueName("CustomKeyStoreProviderTestTable"), columnEncryptionKeys[2], columnEncryptionKeys[0], useDeterministicEncryption: true); - tables.Add(CustomKeyStoreProviderTestTable); + if (columnEncryptionKeys.Count > 2) + { + // columnEncryptionKeys[2] is encrypted with DummyCMK. use this encrypted column to test custom key store providers + CustomKeyStoreProviderTestTable = new ApiTestTable(GenerateUniqueName("CustomKeyStoreProviderTestTable"), columnEncryptionKeys[2], columnEncryptionKeys[0], useDeterministicEncryption: true); + tables.Add(CustomKeyStoreProviderTestTable); + } TabNVarCharMaxSource = new BulkCopyTruncationTables(GenerateUniqueName("TabNVarCharMaxSource"), columnEncryptionKeys[0]); tables.Add(TabNVarCharMaxSource); @@ -259,13 +273,7 @@ protected List
    CreateTables(IList columnEncryptionKe protected string GenerateUniqueName(string baseName) => string.Concat("AE-", baseName, "-", Guid.NewGuid().ToString()); - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) + protected override void Dispose(bool disposing) { databaseObjects.Reverse(); foreach (string value in DataTestUtility.AEConnStringsSetup) @@ -276,6 +284,7 @@ protected virtual void Dispose(bool disposing) databaseObjects.ForEach(o => o.Drop(sqlConnection)); } } + base.Dispose(disposing); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs index 0c593ab8da..f1ebc3a93e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs @@ -4,6 +4,10 @@ using System; using System.Collections.Generic; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using Azure; +using Azure.Security.KeyVault.Keys; using Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider; using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; @@ -13,17 +17,25 @@ public class SQLSetupStrategyAzureKeyVault : SQLSetupStrategy { internal static bool IsAKVProviderRegistered = false; + private readonly List _akvKeyNames; + private readonly KeyClient _keyClient; + public Table AKVTestTable { get; private set; } public SqlColumnEncryptionAzureKeyVaultProvider AkvStoreProvider; public DummyMasterKeyForAKVProvider DummyMasterKey; + public string AkvKeyUrl { get; private set; } public SQLSetupStrategyAzureKeyVault() : base() { + _akvKeyNames = new List(); + _keyClient = new KeyClient(DataTestUtility.AKVBaseUri, DataTestUtility.GetTokenCredential()); AkvStoreProvider = new SqlColumnEncryptionAzureKeyVaultProvider(new SqlClientCustomTokenCredential()); + if (!IsAKVProviderRegistered) { RegisterGlobalProviders(AkvStoreProvider); } + SetupAzureKeyVault(); SetupDatabase(); } @@ -42,10 +54,42 @@ public static void RegisterGlobalProviders(SqlColumnEncryptionAzureKeyVaultProvi IsAKVProviderRegistered = true; } + private static RSA CopyKey(RSA rsa) + { +#if NET8_0 + // In .NET Framework, the key is exportable in plaintext. In .NET 9.0+, we use X509CertificateLoader2 to maintain this functionality. + // We need to manually handle this in .NET 8.0 with an non-plaintext export. + RSA replacementKey = RSA.Create(rsa.KeySize); + Span passwordBytes = stackalloc byte[32]; + PbeParameters pbeParameters = new PbeParameters(PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, 10000); + + Random.Shared.NextBytes(passwordBytes); + + replacementKey.ImportEncryptedPkcs8PrivateKey( + passwordBytes, + rsa.ExportEncryptedPkcs8PrivateKey(passwordBytes, pbeParameters), + out _); + return replacementKey; +#else + return rsa; +#endif + } + + private void SetupAzureKeyVault() + { + RSA rsaCopy = CopyKey(ColumnMasterKeyCertificate.GetRSAPrivateKey()); + JsonWebKey rsaImport = new JsonWebKey(rsaCopy, true); + string akvKeyName = $"AE-{ColumnMasterKeyCertificate.Thumbprint}"; + + _keyClient.ImportKey(akvKeyName, rsaImport); + _akvKeyNames.Add(akvKeyName); + AkvKeyUrl = (new Uri(DataTestUtility.AKVBaseUri, $"/keys/{akvKeyName}")).AbsoluteUri; + } + internal override void SetupDatabase() { - ColumnMasterKey akvColumnMasterKey = new AkvColumnMasterKey(GenerateUniqueName("AKVCMK"), akvUrl: DataTestUtility.AKVUrl, AkvStoreProvider, DataTestUtility.EnclaveEnabled); - DummyMasterKey = new DummyMasterKeyForAKVProvider(GenerateUniqueName("DummyCMK"), DataTestUtility.AKVUrl, AkvStoreProvider, false); + ColumnMasterKey akvColumnMasterKey = new AkvColumnMasterKey(GenerateUniqueName("AKVCMK"), akvUrl: AkvKeyUrl, AkvStoreProvider, DataTestUtility.EnclaveEnabled); + DummyMasterKey = new DummyMasterKeyForAKVProvider(GenerateUniqueName("DummyCMK"), AkvKeyUrl, AkvStoreProvider, false); databaseObjects.Add(akvColumnMasterKey); databaseObjects.Add(DummyMasterKey); @@ -62,5 +106,22 @@ internal override void SetupDatabase() base.SetupDatabase(); } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + foreach (string keyName in _akvKeyNames) + { + try + { + _keyClient.StartDeleteKey(keyName); + } + catch (Exception) + { + continue; + } + } + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCertStoreProvider.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCertStoreProvider.cs index ae71dfc446..db935a5fed 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCertStoreProvider.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCertStoreProvider.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information.using System; using System.Collections.Generic; -using System.Security.Cryptography.X509Certificates; using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted @@ -20,12 +19,10 @@ public SQLSetupStrategyCertStoreProvider() : base() SetupDatabase(); } - protected SQLSetupStrategyCertStoreProvider(string customKeyPath) => keyPath = customKeyPath; - internal override void SetupDatabase() { - CspColumnMasterKey = new CspColumnMasterKey(GenerateUniqueName("CMK"), certificate.Thumbprint, CertStoreProvider, DataTestUtility.EnclaveEnabled); - DummyMasterKey = new DummyMasterKeyForCertStoreProvider(GenerateUniqueName("DummyCMK"), certificate.Thumbprint, CertStoreProvider, false); + CspColumnMasterKey = new CspColumnMasterKey(GenerateUniqueName("CMK"), ColumnMasterKeyCertificate.Thumbprint, CertStoreProvider, DataTestUtility.EnclaveEnabled); + DummyMasterKey = new DummyMasterKeyForCertStoreProvider(GenerateUniqueName("DummyCMK"), ColumnMasterKeyCertificate.Thumbprint, CertStoreProvider, false); databaseObjects.Add(CspColumnMasterKey); databaseObjects.Add(DummyMasterKey); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspExt.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspExt.cs deleted file mode 100644 index 3b965d5e7f..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspExt.cs +++ /dev/null @@ -1,50 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information.using System; - -using System.Collections.Generic; -using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; - -namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted -{ - public class SQLSetupStrategyCspExt : SQLSetupStrategyCertStoreProvider - { - public Table CspProviderTable { get; private set; } - public SqlColumnEncryptionCspProvider keyStoreProvider { get; } - - public SQLSetupStrategyCspExt(string cspKeyPath) : base(cspKeyPath) - { - keyStoreProvider = new SqlColumnEncryptionCspProvider(); - this.SetupDatabase(); - } - - internal override void SetupDatabase() - { - ColumnMasterKey columnMasterKey = new CspProviderColumnMasterKey(GenerateUniqueName("CspExt"), SqlColumnEncryptionCspProvider.ProviderName, keyPath); - databaseObjects.Add(columnMasterKey); - - List columnEncryptionKeys = CreateColumnEncryptionKeys(columnMasterKey, 2, keyStoreProvider); - databaseObjects.AddRange(columnEncryptionKeys); - - Table table = CreateTable(columnEncryptionKeys); - databaseObjects.Add(table); - - foreach(string value in DataTestUtility.AEConnStringsSetup) - { - using (SqlConnection sqlConnection = new SqlConnection(value)) - { - sqlConnection.Open(); - databaseObjects.ForEach(o => o.Create(sqlConnection)); - } - } - - } - - private Table CreateTable(IList columnEncryptionKeys) - { - CspProviderTable = new ApiTestTable(GenerateUniqueName("CspProviderTable"), columnEncryptionKeys[0], columnEncryptionKeys[1]); - - return CspProviderTable; - } - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspProvider.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspProvider.cs new file mode 100644 index 0000000000..08cdfe48ec --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCspProvider.cs @@ -0,0 +1,77 @@ +// 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; +using System.Collections.Generic; +using System.Security.Cryptography; +using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted +{ + public class SQLSetupStrategyCspProvider : SQLSetupStrategy + { + private const int KeySize = 2048; + + private readonly List _cspKeyParameters = new List(); + + public SQLSetupStrategyCspProvider(CspParameters cspParameters) + : base(cspParameters.ProviderName + "/" + cspParameters.KeyContainerName) + { + // Create a new instance of RSACryptoServiceProvider to generate + // a new key pair. Pass the CspParameters class to persist the + // key in the container. + using RSACryptoServiceProvider rsaAlg = new RSACryptoServiceProvider(KeySize, cspParameters); + rsaAlg.PersistKeyInCsp = true; + + _cspKeyParameters.Add(cspParameters); + + CspProvider = new SqlColumnEncryptionCspProvider(); + SetupDatabase(); + } + + public SqlColumnEncryptionCspProvider CspProvider { get; } + + internal override void SetupDatabase() + { + ColumnMasterKey columnMasterKey = new CspProviderColumnMasterKey(GenerateUniqueName("CspExt"), SqlColumnEncryptionCspProvider.ProviderName, ColumnMasterKeyPath); + databaseObjects.Add(columnMasterKey); + + List columnEncryptionKeys = CreateColumnEncryptionKeys(columnMasterKey, 2, CspProvider); + databaseObjects.AddRange(columnEncryptionKeys); + + List
    tables = CreateTables(columnEncryptionKeys); + databaseObjects.AddRange(tables); + + base.SetupDatabase(); + + InsertSampleData(ApiTestTable.Name); + } + + protected override void Dispose(bool disposing) + { + foreach (CspParameters cspParameters in _cspKeyParameters) + { + try + { + // Create a new instance of RSACryptoServiceProvider. + // Pass the CspParameters class to use the + // key in the container. + using RSACryptoServiceProvider rsaAlg = new RSACryptoServiceProvider(cspParameters); + + // Delete the key entry in the container. + rsaAlg.PersistKeyInCsp = false; + + // Call Clear to release resources and delete the key from the container. + rsaAlg.Clear(); + } + catch (Exception) + { + continue; + } + } + + base.Dispose(disposing); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs index 0f5b5a9216..7e564362a5 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtility.cs @@ -102,102 +102,16 @@ internal static void CleanSqlClientCache() ClearCache(cache); } - /// - /// Create a self-signed certificate. - /// - internal static X509Certificate2 CreateCertificate() - { - byte[] certificateRawBytes = new byte[] { 48, 130, 10, 36, 2, 1, 3, 48, 130, 9, 224, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 160, 130, 9, 209, 4, 130, 9, 205, 48, 130, 9, 201, 48, 130, 5, 250, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 160, 130, 5, 235, 4, 130, 5, 231, 48, 130, 5, 227, 48, 130, 5, 223, 6, 11, 42, 134, 72, 134, 247, 13, 1, 12, 10, 1, 2, 160, 130, 4, 254, 48, 130, 4, 250, 48, 28, 6, 10, 42, 134, 72, 134, 247, 13, 1, 12, 1, 3, 48, 14, 4, 8, 146, 126, 191, 6, 130, 18, 111, 71, 2, 2, 7, 208, 4, 130, 4, 216, 55, 138, 10, 135, 82, 84, 240, 82, 107, 75, 21, 156, 54, 53, 188, 62, 36, 248, 59, 17, 18, 41, 206, 171, 226, 168, 175, 59, 48, 50, 36, 26, 58, 39, 118, 231, 200, 107, 86, 144, 200, 20, 135, 22, 105, 159, 229, 116, 123, 122, 194, 69, 172, 171, 128, 251, 129, 222, 113, 27, 253, 48, 164, 116, 72, 194, 123, 12, 247, 186, 162, 40, 39, 114, 22, 118, 91, 192, 73, 122, 235, 247, 40, 89, 3, 222, 64, 214, 184, 67, 204, 188, 197, 188, 107, 126, 225, 194, 161, 110, 156, 45, 70, 26, 86, 69, 63, 120, 153, 164, 136, 15, 220, 153, 104, 50, 121, 87, 10, 180, 149, 98, 220, 73, 175, 50, 146, 231, 112, 230, 204, 132, 76, 43, 142, 7, 104, 142, 146, 92, 21, 52, 38, 59, 154, 108, 159, 192, 93, 174, 39, 134, 96, 189, 150, 77, 90, 160, 43, 127, 173, 199, 189, 4, 69, 44, 104, 148, 225, 44, 149, 167, 149, 121, 220, 232, 98, 131, 212, 130, 35, 79, 10, 173, 177, 150, 161, 91, 26, 12, 221, 136, 230, 124, 73, 96, 126, 12, 241, 99, 60, 140, 126, 140, 0, 166, 47, 16, 87, 102, 138, 45, 97, 21, 31, 224, 126, 231, 102, 99, 35, 207, 75, 22, 249, 115, 51, 106, 79, 208, 21, 108, 124, 143, 108, 130, 6, 61, 215, 227, 7, 224, 174, 193, 97, 211, 241, 224, 90, 37, 101, 147, 149, 173, 239, 113, 214, 1, 41, 69, 158, 203, 3, 63, 101, 196, 134, 7, 127, 58, 113, 243, 228, 162, 99, 75, 207, 153, 19, 193, 187, 52, 124, 85, 234, 7, 249, 75, 65, 230, 107, 247, 145, 64, 94, 106, 50, 117, 83, 138, 49, 10, 22, 211, 115, 183, 20, 119, 18, 117, 166, 153, 30, 210, 248, 118, 200, 21, 180, 118, 208, 53, 90, 243, 74, 76, 109, 106, 46, 103, 112, 197, 89, 92, 178, 83, 48, 97, 162, 73, 78, 105, 145, 213, 230, 17, 211, 121, 200, 101, 179, 158, 85, 99, 211, 68, 122, 234, 176, 4, 33, 225, 120, 139, 163, 110, 35, 199, 23, 45, 126, 199, 80, 145, 14, 74, 217, 200, 172, 216, 159, 237, 241, 157, 85, 210, 141, 180, 150, 187, 82, 48, 245, 154, 125, 60, 223, 244, 21, 20, 39, 88, 8, 153, 185, 227, 76, 78, 137, 99, 98, 81, 141, 27, 197, 41, 39, 251, 80, 27, 85, 78, 65, 15, 216, 106, 106, 113, 33, 253, 210, 46, 214, 47, 49, 89, 170, 215, 207, 62, 182, 88, 25, 186, 166, 214, 172, 63, 94, 17, 123, 235, 226, 72, 73, 204, 18, 173, 134, 92, 66, 2, 213, 151, 251, 95, 175, 38, 56, 156, 138, 96, 123, 190, 107, 59, 230, 24, 210, 224, 206, 169, 159, 95, 180, 237, 34, 194, 62, 4, 213, 228, 85, 216, 138, 157, 50, 20, 101, 160, 195, 138, 207, 18, 17, 232, 6, 73, 82, 247, 173, 50, 180, 53, 58, 156, 97, 230, 112, 211, 251, 204, 120, 188, 34, 41, 67, 83, 197, 131, 251, 176, 20, 70, 169, 116, 237, 43, 117, 45, 31, 66, 74, 152, 216, 3, 108, 102, 99, 5, 127, 76, 129, 57, 180, 90, 218, 157, 108, 85, 4, 240, 101, 149, 154, 221, 208, 70, 152, 34, 128, 57, 135, 38, 17, 139, 142, 167, 109, 73, 129, 181, 105, 45, 151, 106, 171, 166, 0, 113, 147, 141, 19, 228, 196, 88, 175, 219, 18, 213, 54, 105, 179, 8, 249, 250, 164, 86, 28, 185, 19, 60, 50, 140, 73, 237, 148, 201, 33, 204, 189, 43, 83, 163, 138, 1, 10, 13, 240, 196, 211, 221, 169, 207, 100, 167, 203, 146, 115, 70, 118, 230, 4, 224, 192, 209, 242, 144, 150, 72, 170, 149, 255, 196, 7, 91, 55, 251, 57, 127, 103, 98, 113, 83, 224, 97, 118, 132, 81, 119, 8, 105, 250, 155, 107, 149, 28, 127, 66, 127, 224, 79, 96, 9, 168, 73, 84, 228, 123, 161, 222, 179, 115, 73, 184, 62, 24, 228, 44, 156, 42, 124, 209, 29, 81, 19, 169, 24, 212, 6, 238, 239, 221, 68, 220, 106, 0, 45, 201, 129, 3, 50, 150, 244, 32, 220, 237, 20, 39, 175, 249, 80, 189, 166, 68, 251, 102, 60, 137, 93, 209, 86, 194, 55, 164, 100, 76, 220, 249, 30, 233, 101, 177, 150, 71, 28, 227, 180, 44, 115, 83, 201, 129, 44, 128, 247, 68, 175, 97, 36, 170, 76, 236, 57, 119, 240, 0, 129, 185, 35, 160, 231, 183, 56, 162, 197, 237, 186, 109, 118, 232, 84, 108, 125, 93, 92, 101, 193, 180, 210, 192, 244, 47, 55, 56, 217, 178, 200, 168, 232, 80, 223, 209, 255, 234, 146, 46, 215, 170, 197, 94, 84, 213, 233, 140, 247, 69, 185, 103, 183, 91, 23, 232, 32, 246, 244, 30, 41, 156, 28, 72, 109, 90, 127, 135, 132, 19, 136, 233, 168, 29, 98, 17, 111, 5, 185, 234, 86, 234, 114, 47, 227, 81, 77, 108, 179, 184, 91, 31, 74, 23, 29, 248, 41, 207, 8, 23, 181, 33, 99, 217, 48, 145, 97, 126, 139, 133, 11, 100, 69, 151, 146, 38, 79, 231, 155, 92, 134, 139, 189, 237, 132, 196, 95, 45, 141, 15, 26, 37, 58, 219, 10, 0, 36, 221, 240, 82, 117, 163, 121, 141, 206, 21, 180, 195, 58, 109, 56, 123, 152, 206, 116, 161, 221, 125, 248, 23, 31, 240, 227, 186, 52, 171, 147, 51, 39, 203, 92, 205, 182, 146, 149, 111, 27, 59, 219, 234, 216, 52, 89, 22, 224, 76, 62, 94, 76, 131, 48, 162, 134, 161, 177, 44, 205, 101, 253, 13, 237, 40, 29, 72, 224, 121, 74, 189, 57, 81, 58, 169, 178, 173, 157, 182, 143, 205, 64, 225, 137, 188, 235, 43, 195, 3, 187, 105, 113, 72, 82, 153, 58, 97, 38, 251, 212, 149, 191, 11, 153, 157, 106, 16, 236, 237, 209, 210, 208, 19, 68, 92, 176, 65, 24, 115, 181, 94, 24, 126, 2, 216, 63, 200, 136, 178, 92, 248, 11, 128, 68, 122, 14, 46, 234, 48, 142, 219, 92, 29, 136, 70, 200, 52, 78, 70, 160, 215, 113, 102, 190, 66, 16, 69, 120, 25, 201, 23, 209, 41, 79, 25, 151, 38, 38, 82, 244, 143, 121, 216, 111, 91, 167, 232, 32, 234, 243, 195, 168, 240, 135, 188, 1, 92, 145, 77, 240, 107, 20, 82, 147, 168, 132, 78, 115, 206, 95, 47, 8, 80, 91, 255, 28, 38, 161, 52, 168, 211, 236, 143, 238, 146, 172, 104, 2, 254, 240, 229, 210, 225, 47, 41, 76, 134, 5, 20, 203, 188, 48, 195, 120, 103, 234, 94, 217, 142, 238, 254, 131, 146, 214, 106, 212, 229, 201, 79, 151, 198, 100, 132, 99, 228, 82, 182, 94, 216, 226, 163, 42, 113, 110, 201, 70, 221, 127, 242, 7, 176, 60, 121, 158, 37, 56, 6, 156, 191, 75, 94, 222, 10, 155, 39, 64, 172, 216, 106, 210, 202, 246, 66, 83, 107, 250, 17, 134, 222, 212, 71, 200, 215, 103, 35, 82, 225, 106, 17, 106, 74, 18, 130, 236, 175, 45, 145, 155, 169, 88, 72, 244, 3, 38, 245, 208, 49, 129, 205, 48, 19, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 21, 49, 6, 4, 4, 1, 0, 0, 0, 48, 87, 6, 9, 42, 134, 72, 134, 247, 13, 1, 9, 20, 49, 74, 30, 72, 0, 100, 0, 99, 0, 99, 0, 52, 0, 51, 0, 48, 0, 56, 0, 56, 0, 45, 0, 50, 0, 57, 0, 54, 0, 53, 0, 45, 0, 52, 0, 57, 0, 97, 0, 48, 0, 45, 0, 56, 0, 51, 0, 54, 0, 53, 0, 45, 0, 50, 0, 52, 0, 101, 0, 52, 0, 97, 0, 52, 0, 49, 0, 100, 0, 55, 0, 50, 0, 52, 0, 48, 48, 93, 6, 9, 43, 6, 1, 4, 1, 130, 55, 17, 1, 49, 80, 30, 78, 0, 77, 0, 105, 0, 99, 0, 114, 0, 111, 0, 115, 0, 111, 0, 102, 0, 116, 0, 32, 0, 83, 0, 116, 0, 114, 0, 111, 0, 110, 0, 103, 0, 32, 0, 67, 0, 114, 0, 121, 0, 112, 0, 116, 0, 111, 0, 103, 0, 114, 0, 97, 0, 112, 0, 104, 0, 105, 0, 99, 0, 32, 0, 80, 0, 114, 0, 111, 0, 118, 0, 105, 0, 100, 0, 101, 0, 114, 48, 130, 3, 199, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 6, 160, 130, 3, 184, 48, 130, 3, 180, 2, 1, 0, 48, 130, 3, 173, 6, 9, 42, 134, 72, 134, 247, 13, 1, 7, 1, 48, 28, 6, 10, 42, 134, 72, 134, 247, 13, 1, 12, 1, 3, 48, 14, 4, 8, 206, 244, 28, 93, 203, 68, 165, 233, 2, 2, 7, 208, 128, 130, 3, 128, 74, 136, 80, 43, 195, 182, 181, 122, 132, 229, 10, 181, 229, 1, 78, 122, 145, 95, 16, 236, 242, 107, 9, 141, 186, 205, 32, 139, 154, 132, 184, 180, 80, 26, 3, 85, 196, 10, 33, 216, 101, 105, 172, 196, 77, 222, 232, 229, 37, 199, 6, 189, 152, 8, 203, 15, 231, 164, 140, 163, 120, 23, 137, 34, 16, 241, 186, 64, 11, 241, 210, 160, 186, 90, 55, 39, 21, 210, 145, 74, 151, 40, 122, 221, 240, 191, 185, 115, 85, 208, 125, 136, 51, 210, 137, 124, 155, 65, 135, 50, 35, 233, 223, 157, 131, 108, 11, 142, 152, 217, 162, 163, 218, 47, 89, 255, 229, 21, 224, 139, 187, 4, 175, 251, 248, 8, 18, 16, 112, 134, 75, 17, 90, 246, 62, 150, 31, 207, 95, 172, 5, 220, 135, 201, 179, 247, 193, 177, 23, 5, 170, 207, 66, 219, 145, 117, 99, 167, 238, 100, 158, 169, 44, 22, 199, 132, 38, 67, 203, 66, 187, 53, 216, 98, 113, 76, 142, 153, 36, 238, 110, 152, 251, 68, 6, 154, 255, 51, 65, 75, 91, 9, 121, 86, 116, 35, 224, 47, 220, 194, 17, 136, 175, 76, 165, 210, 153, 89, 104, 197, 133, 200, 49, 173, 1, 167, 5, 88, 183, 58, 193, 146, 30, 60, 129, 195, 3, 16, 78, 87, 167, 135, 182, 182, 150, 68, 116, 161, 116, 125, 180, 155, 103, 63, 0, 98, 27, 179, 142, 64, 73, 31, 35, 63, 138, 137, 30, 169, 149, 221, 104, 21, 182, 23, 67, 246, 2, 162, 217, 165, 238, 124, 229, 149, 84, 5, 203, 174, 149, 79, 153, 25, 153, 233, 213, 86, 250, 10, 42, 6, 226, 113, 123, 90, 76, 153, 39, 203, 237, 124, 36, 191, 232, 132, 127, 82, 163, 109, 100, 121, 54, 254, 116, 155, 26, 255, 50, 150, 140, 172, 240, 208, 245, 65, 72, 49, 183, 149, 220, 244, 120, 193, 37, 222, 144, 137, 82, 168, 233, 13, 179, 2, 217, 29, 177, 4, 136, 69, 192, 133, 249, 180, 9, 62, 162, 216, 251, 164, 188, 173, 143, 149, 32, 204, 255, 246, 249, 33, 216, 75, 23, 127, 215, 134, 69, 79, 112, 213, 198, 89, 44, 51, 19, 226, 16, 210, 125, 212, 232, 18, 252, 178, 93, 245, 33, 62, 81, 207, 78, 167, 144, 238, 251, 27, 194, 21, 53, 44, 63, 58, 26, 176, 75, 79, 164, 67, 59, 80, 17, 54, 209, 58, 184, 2, 36, 202, 135, 91, 35, 78, 55, 203, 134, 238, 79, 178, 84, 242, 46, 223, 131, 227, 87, 255, 182, 244, 117, 162, 60, 134, 161, 49, 59, 95, 64, 190, 30, 195, 100, 106, 7, 120, 181, 202, 122, 174, 234, 30, 11, 88, 65, 238, 53, 64, 243, 233, 185, 168, 34, 8, 58, 233, 171, 210, 104, 105, 93, 49, 206, 11, 40, 172, 248, 204, 80, 128, 53, 143, 54, 95, 92, 70, 152, 209, 193, 116, 252, 138, 19, 50, 249, 43, 14, 225, 167, 8, 205, 112, 103, 79, 223, 14, 141, 147, 70, 197, 91, 11, 117, 202, 19, 180, 240, 21, 118, 108, 25, 63, 54, 94, 156, 112, 109, 16, 216, 113, 192, 246, 207, 156, 203, 65, 75, 143, 157, 125, 158, 151, 167, 207, 96, 6, 162, 97, 66, 114, 95, 227, 52, 44, 98, 121, 139, 181, 240, 89, 27, 59, 156, 189, 93, 28, 48, 165, 11, 245, 102, 198, 29, 5, 6, 180, 147, 58, 130, 65, 201, 10, 164, 193, 93, 168, 96, 156, 89, 225, 139, 70, 245, 74, 128, 3, 141, 133, 137, 21, 163, 77, 3, 19, 226, 35, 248, 156, 56, 56, 37, 221, 69, 67, 214, 3, 152, 149, 224, 92, 72, 173, 39, 196, 229, 153, 67, 151, 190, 115, 20, 70, 126, 210, 140, 109, 186, 46, 82, 88, 185, 96, 1, 254, 161, 217, 130, 226, 133, 18, 103, 175, 132, 249, 102, 51, 229, 192, 94, 44, 10, 25, 197, 237, 77, 196, 1, 253, 153, 78, 237, 151, 136, 89, 203, 113, 244, 217, 235, 252, 31, 116, 139, 233, 40, 197, 22, 176, 157, 130, 109, 149, 215, 11, 20, 3, 156, 239, 29, 250, 95, 188, 241, 184, 117, 108, 216, 74, 91, 169, 186, 122, 175, 214, 36, 62, 240, 142, 107, 172, 7, 250, 31, 101, 75, 83, 255, 56, 8, 231, 200, 194, 154, 105, 202, 170, 207, 252, 128, 10, 249, 53, 41, 168, 94, 225, 163, 10, 251, 149, 64, 10, 144, 252, 44, 136, 149, 119, 183, 7, 230, 87, 160, 46, 62, 185, 82, 218, 213, 125, 62, 70, 43, 27, 5, 181, 50, 193, 11, 30, 0, 8, 81, 94, 169, 171, 143, 113, 235, 171, 38, 129, 116, 11, 191, 75, 235, 185, 184, 178, 36, 193, 174, 177, 51, 87, 163, 142, 52, 62, 161, 237, 139, 50, 51, 227, 188, 164, 106, 233, 209, 8, 237, 241, 92, 145, 51, 6, 36, 197, 24, 255, 143, 5, 144, 43, 87, 242, 208, 251, 79, 171, 90, 103, 219, 73, 242, 95, 36, 48, 95, 127, 40, 128, 201, 80, 79, 74, 226, 25, 43, 50, 56, 180, 59, 84, 148, 110, 151, 9, 45, 4, 212, 172, 31, 189, 44, 115, 59, 169, 48, 59, 48, 31, 48, 7, 6, 5, 43, 14, 3, 2, 26, 4, 20, 238, 91, 24, 104, 64, 45, 237, 63, 114, 36, 111, 106, 82, 43, 251, 110, 60, 159, 42, 178, 4, 20, 20, 49, 70, 55, 115, 247, 221, 156, 47, 189, 197, 19, 116, 77, 161, 163, 216, 77, 166, 144, 2, 2, 7, 208 }; - X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12(certificateRawBytes, "", X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.UserKeySet | X509KeyStorageFlags.Exportable); - X509Store certStore = null; - try - { - certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); - certStore.Open(OpenFlags.ReadWrite); - if (!certStore.Certificates.Contains(certificate)) - { - certStore.Add(certificate); - } - - } - finally - { - if (certStore != null) - { - certStore.Close(); - } - } - - if (DataTestUtility.IsAKVSetupAvailable()) - { - SetupAKVKeysAsync().Wait(); - } - - return certificate; - } - - private static async Task SetupAKVKeysAsync() - { - KeyClient keyClient = new KeyClient(DataTestUtility.AKVBaseUri, DataTestUtility.GetTokenCredential()); - AsyncPageable keys = keyClient.GetPropertiesOfKeysAsync(); - IAsyncEnumerator enumerator = keys.GetAsyncEnumerator(); - - bool testAKVKeyExists = false; - try - { - while (await enumerator.MoveNextAsync()) - { - KeyProperties keyProperties = enumerator.Current; - if (keyProperties.Name.Equals(DataTestUtility.AKVKeyName)) - { - testAKVKeyExists = true; - } - } - } - finally - { - await enumerator.DisposeAsync(); - } - - if (!testAKVKeyExists) - { - var rsaKeyOptions = new CreateRsaKeyOptions(DataTestUtility.AKVKeyName, hardwareProtected: false) - { - KeySize = 2048, - ExpiresOn = DateTimeOffset.Now.AddYears(1) - }; - keyClient.CreateRsaKey(rsaKeyOptions); - } - } - - /// - /// Removes a certificate from the local certificate store (useful for test cleanup). - /// - internal static void RemoveCertificate(X509Certificate2 certificate) - { - X509Store certStore = null; - try - { - certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); - certStore.Open(OpenFlags.ReadWrite); - certStore.Remove(certificate); - } - finally - { - if (certStore != null) - { - certStore.Close(); - } - } - } - internal static byte[] DecryptRsaDirectly(byte[] rsaPfx, byte[] ciphertextCek, string masterKeyPath) { Debug.Assert(rsaPfx != null && rsaPfx.Length > 0); // The rest of the parameters may be invalid for exception handling test cases +#if NET9_0_OR_GREATER X509Certificate2 x509 = X509CertificateLoader.LoadPkcs12(rsaPfx, @"P@zzw0rD!SqlvN3x+"); - +#else + X509Certificate2 x509 = new(rsaPfx, @"P@zzw0rD!SqlvN3x+"); +#endif Debug.Assert(x509.HasPrivateKey); SqlColumnEncryptionCertificateStoreProvider rsaProvider = new SqlColumnEncryptionCertificateStoreProvider(); @@ -210,8 +124,11 @@ internal static bool VerifyRsaSignatureDirectly(byte[] hashedCek, byte[] signedC { Debug.Assert(rsaPfx != null && rsaPfx.Length > 0); +#if NET9_0_OR_GREATER X509Certificate2 x509 = X509CertificateLoader.LoadPkcs12(rsaPfx, @"P@zzw0rD!SqlvN3x+"); - +#else + X509Certificate2 x509 = new(rsaPfx, @"P@zzw0rD!SqlvN3x+"); +#endif Debug.Assert(x509.HasPrivateKey); SqlColumnEncryptionCertificateStoreProvider rsaProvider = new SqlColumnEncryptionCertificateStoreProvider(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtilityWin.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtilityWin.cs deleted file mode 100644 index f1c0f5f9c1..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/CertificateUtilityWin.cs +++ /dev/null @@ -1,264 +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; -using System.Diagnostics; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using Xunit; -using System.Collections.Generic; -using static System.Net.Mime.MediaTypeNames; -#if !NETFRAMEWORK -using System.Runtime.Versioning; -#endif - -namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted -{ -#if !NETFRAMEWORK - [SupportedOSPlatform("windows")] -#endif - [PlatformSpecific(TestPlatforms.Windows)] - class CertificateUtilityWin - { - - public class CertificateOption - { - public string Subject { get; set; } - public string KeyExportPolicy { get; set; } = "Exportable"; - public string CertificateStoreLocation { get; set; } - public List> TextExtensions { get; set; } = new List> { new Tuple("2.5.29.3", "1.3.6.1.5.5.8.2.2", "1.3.6.1.4.1.311.10.3.11") }; - public string KeySpec { get; set; } = "KeyExchange"; - public string Provider { get; set; } = "Microsoft Enhanced RSA and AES Cryptographic Provider"; - public string Type { get; set; } = "24"; - public string KeyAlgorithm { get; set; } = "rsa"; - public string KeyLength { get; set; } = "2048"; - public string HashAlgorithm { get; set; } = "sha256"; - public string KeyUsage { get; set; } = "None"; - public string TestRoot { get; set; } = "-TestRoot"; - } - - private CertificateUtilityWin() - { - } - - /// - /// Create a self-signed certificate through PowerShell. - /// - internal static void CreateCertificate(CertificateOption option) - { - Assert.False(string.IsNullOrWhiteSpace(option.Subject), "FAILED: certificateName should not be null or empty."); - Assert.False(string.IsNullOrWhiteSpace(option.CertificateStoreLocation), "FAILED: certificateLocation should not be null or empty."); - - string powerShellPath = string.IsNullOrEmpty(DataTestUtility.PowerShellPath) ? "powershell.exe" : DataTestUtility.PowerShellPath; - ProcessStartInfo processStartInfo = new ProcessStartInfo(powerShellPath) - { - Arguments = $"New-SelfSignedCertificate -Subject " + - $"'CN={option.Subject}' " + - $"-KeyExportPolicy {option.KeyExportPolicy} " + - $"-CertStoreLocation '{option.CertificateStoreLocation}\\My' " + - $"-TextExtension @('{option.TextExtensions[0].Item1}={{text}}{option.TextExtensions[0].Item2},{option.TextExtensions[0].Item3}') " + - $"-KeySpec {option.KeySpec} " + - $"-Provider '{option.Provider}' " + - $"-Type {option.Type} " + - $"-KeyAlgorithm {option.KeyAlgorithm} " + - $"-KeyLength {option.KeyLength} " + - $"-HashAlgorithm {option.HashAlgorithm} " + - $"-KeyUsage {option.KeyUsage} " + - $"{option.TestRoot}", - Verb="runas" - }; - Process process = new() - { - StartInfo = processStartInfo - }; - process.StartInfo.UseShellExecute = true; - process.StartInfo.CreateNoWindow = true; - process.Start(); - process.WaitForExit(); - } - - /// - /// Creates an RSA 2048 key inside the specified CSP. - /// - /// CSP name - /// Container name - /// - internal static bool RSAPersistKeyInCsp(string providerName, string containerName) - { - try - { - const int KEYSIZE = 2048; - int providerType = GetProviderKey(providerName); - - // Create a new instance of CspParameters. - CspParameters cspParams = new CspParameters(providerType, providerName, containerName); - - //Create a new instance of RSACryptoServiceProvider to generate - //a new key pair. Pass the CspParameters class to persist the - //key in the container. - RSACryptoServiceProvider rsaAlg = new RSACryptoServiceProvider(KEYSIZE, cspParams); - rsaAlg.PersistKeyInCsp = true; - } - catch (CryptographicException e) - { - Console.WriteLine("\tFAILURE: The RSA key was not persisted in the container, \"{0}\".", containerName); - Console.WriteLine(@" {0}", e.Message); - return false; - } - - return true; - } - - /// - /// Deletes the specified RSA key - /// - /// CSP name - /// Container name to be deleted - /// - internal static bool RSADeleteKeyInCsp(string providerName, string containerName) - { - try - { - int providerType = GetProviderKey(providerName); - - // Create a new instance of CspParameters. - CspParameters cspParams = new CspParameters(providerType, providerName, containerName); - - //Create a new instance of RSACryptoServiceProvider. - //Pass the CspParameters class to use the - //key in the container. - RSACryptoServiceProvider rsaAlg = new RSACryptoServiceProvider(cspParams); - - //Delete the key entry in the container. - rsaAlg.PersistKeyInCsp = false; - - //Call Clear to release resources and delete the key from the container. - rsaAlg.Clear(); - } - catch (CryptographicException e) - { - Console.WriteLine("\tFAILURE: The RSA key was not deleted from the container, \"{0}\".", containerName); - Console.WriteLine("\t{0}", e.Message); - return false; - } - - return true; - } - - internal static int GetProviderKey(string providerName) - { - Microsoft.Win32.RegistryKey key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography\Defaults\Provider"); - Microsoft.Win32.RegistryKey providerKey = key.OpenSubKey(providerName); - return (int)providerKey.GetValue(@"Type"); - } - - /// - /// Checks if a cert exists or not - /// - /// - /// - /// - internal static bool CertificateExists(string certificateName, StoreLocation certificateStoreLocation) - { - Assert.False(string.IsNullOrWhiteSpace(certificateName), "FAILED: certificateName should not be null or empty."); - - X509Store certStore = null; - try - { - certStore = new X509Store(StoreName.My, certificateStoreLocation); - certStore.Open(OpenFlags.ReadOnly); - X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindBySubjectName, certificateName, validOnly: false); - - if (certCollection != null && certCollection.Count > 0) - { - return true; - } - else - { - return false; - } - } - finally - { - if (certStore != null) - { - certStore.Close(); - } - } - } - - /// - /// Gets the certificate. - /// - /// - /// - /// - internal static X509Certificate2 GetCertificate(string certificateName, StoreLocation certificateStoreLocation) - { - Assert.False(string.IsNullOrWhiteSpace(certificateName), "FAILED: certificateName should not be null or empty."); - X509Store certStore = null; - try - { - certStore = new X509Store(StoreName.My, certificateStoreLocation); - certStore.Open(OpenFlags.ReadOnly); - X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindBySubjectName, certificateName, validOnly: false); - Debug.Assert(certCollection != null && certCollection.Count > 0); - return certCollection[0]; - } - finally - { - if (certStore != null) - { - certStore.Close(); - } - } - } - - /// - /// Gets Csp path from a given certificate. - /// - internal static string GetCspPathFromCertificate(X509Certificate2 certificate) - { - if (certificate.GetRSAPrivateKey() is RSACryptoServiceProvider csp) - { - return string.Concat(csp.CspKeyContainerInfo.ProviderName, @"/", csp.CspKeyContainerInfo.KeyContainerName); - } - else - { - return null; - } - } - - /// - /// Removes a certificate from the store. Cleanup purposes. - /// - /// - /// - /// - internal static void RemoveCertificate(string certificateName, StoreLocation certificateStoreLocation) - { - Assert.False(string.IsNullOrWhiteSpace(certificateName), "FAILED: certificateName should not be null or empty."); - - X509Store certStore = null; - try - { - certStore = new X509Store(StoreName.My, certificateStoreLocation); - certStore.Open(OpenFlags.ReadWrite); - X509Certificate2Collection certificateCollection = certStore.Certificates.Find(X509FindType.FindBySubjectName, certificateName, validOnly: false); - - if (certificateCollection != null && certificateCollection.Count > 0) - { - certStore.RemoveRange(certificateCollection); - } - } - finally - { - if (certStore != null) - { - certStore.Close(); - } - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs index c617a990b8..c88b2767ea 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestTrustedMasterKeyPaths.cs @@ -7,14 +7,14 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted { public class TestTrustedMasterKeyPaths : IClassFixture { - private SQLSetupStrategyCertStoreProvider fixture; + private readonly string dummyThumbprint; private readonly string tableName; private readonly string columnMasterKeyPath; public TestTrustedMasterKeyPaths(SQLSetupStrategyCertStoreProvider fixture) { - columnMasterKeyPath = string.Format(@"{0}/{1}/{2}", StoreLocation.CurrentUser.ToString(), @"my", CertificateUtility.CreateCertificate().Thumbprint); - this.fixture = fixture; + dummyThumbprint = new string('F', fixture.ColumnMasterKeyCertificate.Thumbprint.Length); + columnMasterKeyPath = fixture.ColumnMasterKeyPath; tableName = fixture.TrustedMasterKeyPathsTestTable.Name; } @@ -147,18 +147,14 @@ public void TestTrustedColumnEncryptionMasterKeyPathsWithMultipleServers(string } // Add entries for one server - List server1TrustedKeyPaths = new List(); - - // Add some random key paths - foreach (char c in new char[] { 'A', 'B' }) + List server1TrustedKeyPaths = new List() { - string tempThumbprint = new string('F', CertificateUtility.CreateCertificate().Thumbprint.Length); - string invalidKeyPath = string.Format(@"{0}/my/{1}", StoreLocation.CurrentUser.ToString(), tempThumbprint); - server1TrustedKeyPaths.Add(invalidKeyPath); - } - - // Add the key path used by the test - server1TrustedKeyPaths.Add(columnMasterKeyPath); + // Add some random key paths + string.Format(@"{0}/my/{1}", StoreLocation.CurrentUser.ToString(), dummyThumbprint), + string.Format(@"{0}/my/{1}", StoreLocation.CurrentUser.ToString(), dummyThumbprint), + // Add the key path used by the test + columnMasterKeyPath + }; // Add it to the dictionary SqlConnection.ColumnEncryptionTrustedMasterKeyPaths.Add(connBuilder.DataSource, server1TrustedKeyPaths); @@ -277,8 +273,7 @@ FROM [{tableName}] // Prepare dictionary with invalid key path List invalidKeyPathList = new List(); - string tempThumbprint = new string('F', CertificateUtility.CreateCertificate().Thumbprint.Length); - string invalidKeyPath = string.Format(@"{0}/my/{1}", StoreLocation.CurrentUser.ToString(), tempThumbprint); + string invalidKeyPath = string.Format(@"{0}/my/{1}", StoreLocation.CurrentUser.ToString(), dummyThumbprint); invalidKeyPathList.Add(invalidKeyPath); SqlConnection.ColumnEncryptionTrustedMasterKeyPaths.Add(connBuilder.DataSource, invalidKeyPathList); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ConnectionTestParameters.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ConnectionTestParameters.cs index 4b6e7b087b..a1e3134762 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ConnectionTestParameters.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ConnectionTestParameters.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Authentication; using System.Text; using System.Threading.Tasks; using Microsoft.SqlServer.TDS.PreLogin; @@ -26,8 +27,13 @@ public class ConnectionTestParameters public string HostNameInCertificate => _hnic; public bool TestResult => _result; public TDSPreLoginTokenEncryptionType TdsEncryptionType => _encryptionType; + public SslProtocols EncryptionProtocols { get; } public ConnectionTestParameters(TDSPreLoginTokenEncryptionType tdsEncryptionType, SqlConnectionEncryptOption encryptOption, bool trustServerCert, string cert, string hnic, bool result) + : this(tdsEncryptionType, encryptOption, trustServerCert, cert, hnic, SslProtocols.Tls12, result) + { } + + public ConnectionTestParameters(TDSPreLoginTokenEncryptionType tdsEncryptionType, SqlConnectionEncryptOption encryptOption, bool trustServerCert, string cert, string hnic, SslProtocols sslProtocols, bool result) { _encryptionOption = encryptOption; _trustServerCert = trustServerCert; @@ -35,6 +41,7 @@ public ConnectionTestParameters(TDSPreLoginTokenEncryptionType tdsEncryptionType _hnic = hnic; _result = result; _encryptionType = tdsEncryptionType; + EncryptionProtocols = sslProtocols; } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ConnectionTestParametersData.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ConnectionTestParametersData.cs index 5a2e01a77c..1bd53ad2a7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ConnectionTestParametersData.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/ConnectionTestParametersData.cs @@ -10,7 +10,6 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.DataCommon { public class ConnectionTestParametersData { - private const int CASES = 30; private string _empty = string.Empty; // It was advised to store the client certificate in its own folder. private static readonly string s_fullPathToCer = Path.Combine(Directory.GetCurrentDirectory(), "clientcert", "localhostcert.cer"); @@ -22,7 +21,7 @@ public class ConnectionTestParametersData public static IEnumerable GetConnectionTestParameters() { - for (int i = 0; i < CASES; i++) + for (int i = 0; i < Data.ConnectionTestParametersList.Count; i++) { yield return new object[] { Data.ConnectionTestParametersList[i] }; } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 54058a9218..e83e49b79a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -13,6 +13,7 @@ using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; +using System.Reflection; using System.Runtime.InteropServices; using System.Security; using System.Security.Principal; @@ -38,8 +39,6 @@ public static class DataTestUtility public static readonly string AADPasswordConnectionString = null; public static readonly string AADServicePrincipalId = null; public static readonly string AADServicePrincipalSecret = null; - public static readonly string AKVBaseUrl = null; - public static readonly string AKVUrl = null; public static readonly string AKVOriginalUrl = null; public static readonly string AKVTenantId = null; public static readonly string LocalDbAppName = null; @@ -72,7 +71,6 @@ public static class DataTestUtility public static string AADUserIdentityAccessToken = null; public const string ApplicationClientId = "2fd908ad-0664-4344-b9be-cd3e8b574c38"; public const string UdtTestDbName = "UdtTestDb"; - public const string AKVKeyName = "TestSqlClientAzureKeyVaultProvider"; public const string EventSourcePrefix = "Microsoft.Data.SqlClient"; public const string MDSEventSourceName = "Microsoft.Data.SqlClient.EventSource"; public const string AKVEventSourceName = "Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.EventSource"; @@ -94,8 +92,13 @@ public static class DataTestUtility //SQL Server EngineEdition private static string s_sqlServerEngineEdition; - // JSON Coloumn type - public static readonly bool IsJsonSupported = false; + // Currently, only Azure SQL supports vectors and JSON. + // Our CI images with specific SQL Server versions lag + // behind with vector and JSON support. + // JSON Column type + public static readonly bool IsJsonSupported = !IsNotAzureServer(); + // VECTOR column type + public static readonly bool IsVectorSupported = !IsNotAzureServer(); // Azure Synapse EngineEditionId == 6 // More could be read at https://learn.microsoft.com/en-us/sql/t-sql/functions/serverproperty-transact-sql?view=sql-server-ver16#propertyname @@ -199,8 +202,6 @@ static DataTestUtility() if (!string.IsNullOrEmpty(AKVOriginalUrl) && Uri.TryCreate(AKVOriginalUrl, UriKind.Absolute, out AKVBaseUri)) { AKVBaseUri = new Uri(AKVBaseUri, "/"); - AKVBaseUrl = AKVBaseUri.AbsoluteUri; - AKVUrl = (new Uri(AKVBaseUri, $"/keys/{AKVKeyName}")).AbsoluteUri; } AKVTenantId = c.AzureKeyVaultTenantId; @@ -454,14 +455,25 @@ public static bool IsAADAuthorityURLSetup() public static bool IsNotAzureServer() { - return !AreConnStringsSetup() || !Utils.IsAzureSqlServer(new SqlConnectionStringBuilder((TCPConnectionString)).DataSource); + return !AreConnStringsSetup() || !Utils.IsAzureSqlServer(new SqlConnectionStringBuilder(TCPConnectionString).DataSource); + } + + public static bool IsNotNamedInstance() + { + return !AreConnStringsSetup() || !new SqlConnectionStringBuilder(TCPConnectionString).DataSource.Contains(@"\"); + } + + public static bool IsLocalHost() + { + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString); + return ParseDataSource(builder.DataSource, out string hostname, out _, out _) && string.Equals("localhost", hostname, StringComparison.OrdinalIgnoreCase); } // Synapse: Always Encrypted is not supported with Azure Synapse. // Ref: https://feedback.azure.com/forums/307516-azure-synapse-analytics/suggestions/17858869-support-always-encrypted-in-sql-data-warehouse public static bool IsAKVSetupAvailable() { - return !string.IsNullOrEmpty(AKVUrl) && !string.IsNullOrEmpty(UserManagedIdentityClientId) && !string.IsNullOrEmpty(AKVTenantId) && IsNotAzureSynapse(); + return AKVBaseUri != null && !string.IsNullOrEmpty(UserManagedIdentityClientId) && !string.IsNullOrEmpty(AKVTenantId) && IsNotAzureSynapse(); } private static readonly DefaultAzureCredential s_defaultCredential = new(new DefaultAzureCredentialOptions { ManagedIdentityClientId = UserManagedIdentityClientId }); @@ -603,6 +615,17 @@ public static void CreateTable(SqlConnection sqlConnection, string tableName, st } } + public static void CreateSP(SqlConnection sqlConnection, string spName, string spBody) + { + DropStoredProcedure(sqlConnection, spName); + string spCreate = "CREATE PROCEDURE " + spName + spBody; + using (SqlCommand command = sqlConnection.CreateCommand()) + { + command.CommandText = spCreate; + command.ExecuteNonQuery(); + } + } + public static void DropTable(SqlConnection sqlConnection, string tableName) { ResurrectConnection(sqlConnection); @@ -1005,7 +1028,33 @@ public class AKVEventListener : BaseEventListener public class MDSEventListener : BaseEventListener { + private static Type s_activityCorrelatorType = Type.GetType("Microsoft.Data.Common.ActivityCorrelator, Microsoft.Data.SqlClient"); + private static PropertyInfo s_currentActivityProperty = s_activityCorrelatorType.GetProperty("Current", BindingFlags.NonPublic | BindingFlags.Static); + + public readonly HashSet ActivityIDs = new(); + public override string Name => MDSEventSourceName; + + protected override void OnMatchingEventWritten(EventWrittenEventArgs eventData) + { + object currentActivity = s_currentActivityProperty.GetValue(null); + string activityId = GetActivityId(currentActivity); + + ActivityIDs.Add(activityId); + } + + private static string GetActivityId(object currentActivity) + { + Type activityIdType = currentActivity.GetType(); + FieldInfo idField = activityIdType.GetField("Id", BindingFlags.NonPublic | BindingFlags.Instance); + FieldInfo sequenceField = activityIdType.GetField("Sequence", BindingFlags.NonPublic | BindingFlags.Instance); + + Guid id = (Guid)idField.GetValue(currentActivity); + uint sequence = (uint)sequenceField.GetValue(currentActivity); + + // This activity ID format matches the one used in the XEvents trace + return string.Format("{0}-{1}", id, sequence).ToUpper(); + } } public class BaseEventListener : EventListener @@ -1034,6 +1083,103 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) { IDs.Add(eventData.EventId); EventData.Add(eventData); + OnMatchingEventWritten(eventData); + } + } + + protected virtual void OnMatchingEventWritten(EventWrittenEventArgs eventData) + { + } + } + + public readonly ref struct XEventScope // : IDisposable + { + private readonly SqlConnection _connection; + private readonly bool _useDatabaseSession; + + public string SessionName { get; } + + public XEventScope(SqlConnection connection, string eventSpecification, string targetSpecification) + { + SessionName = GenerateRandomCharacters("Session"); + _connection = connection; + // SQL Azure only supports database-scoped XEvent sessions + _useDatabaseSession = GetSqlServerProperty(connection.ConnectionString, "EngineEdition") == "5"; + + SetupXEvent(eventSpecification, targetSpecification); + } + + public void Dispose() + => DropXEvent(); + + public System.Xml.XmlDocument GetEvents() + { + string xEventQuery = _useDatabaseSession + ? $@"SELECT xet.target_data + FROM sys.dm_xe_database_session_targets AS xet + INNER JOIN sys.dm_xe_database_sessions AS xe + ON (xe.address = xet.event_session_address) + WHERE xe.name = '{SessionName}'" + : $@"SELECT xet.target_data + FROM sys.dm_xe_session_targets AS xet + INNER JOIN sys.dm_xe_sessions AS xe + ON (xe.address = xet.event_session_address) + WHERE xe.name = '{SessionName}'"; + + using (SqlCommand command = new SqlCommand(xEventQuery, _connection)) + { + if (_connection.State == ConnectionState.Closed) + { + _connection.Open(); + } + + string targetData = command.ExecuteScalar() as string; + System.Xml.XmlDocument xmlDocument = new System.Xml.XmlDocument(); + + xmlDocument.LoadXml(targetData); + return xmlDocument; + } + } + + private void SetupXEvent(string eventSpecification, string targetSpecification) + { + string sessionLocation = _useDatabaseSession ? "DATABASE" : "SERVER"; + string xEventCreateAndStartCommandText = $@"CREATE EVENT SESSION [{SessionName}] ON {sessionLocation} + {eventSpecification} + {targetSpecification} + WITH ( + MAX_MEMORY=4096 KB, + EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS, + MAX_DISPATCH_LATENCY=30 SECONDS, + MAX_EVENT_SIZE=0 KB, + MEMORY_PARTITION_MODE=NONE, + TRACK_CAUSALITY=ON, + STARTUP_STATE=OFF) + + ALTER EVENT SESSION [{SessionName}] ON {sessionLocation} STATE = START "; + + using (SqlCommand createXEventSession = new SqlCommand(xEventCreateAndStartCommandText, _connection)) + { + if (_connection.State == ConnectionState.Closed) + { + _connection.Open(); + } + + createXEventSession.ExecuteNonQuery(); + } + } + + private void DropXEvent() + { + string dropXEventSessionCommand = _useDatabaseSession + ? $"IF EXISTS (select * from sys.dm_xe_database_sessions where name ='{SessionName}')" + + $" DROP EVENT SESSION [{SessionName}] ON DATABASE" + : $"IF EXISTS (select * from sys.dm_xe_sessions where name ='{SessionName}')" + + $" DROP EVENT SESSION [{SessionName}] ON SERVER"; + + using (SqlCommand command = new SqlCommand(dropXEventSessionCommand, _connection)) + { + command.ExecuteNonQuery(); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs index fa57a93697..f9aa8b5664 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/SqlClientCustomTokenCredential.cs @@ -16,10 +16,10 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests public class SqlClientCustomTokenCredential : TokenCredential { private const string DEFAULT_PREFIX = "/.default"; + private const string AKVKeyName = "TestSqlClientAzureKeyVaultProvider"; string _authority = ""; string _resource = ""; - string _akvUrl = ""; public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) => AcquireTokenAsync().GetAwaiter().GetResult(); @@ -31,11 +31,12 @@ private async Task AcquireTokenAsync() { // Added to reduce HttpClient calls. // For multi-user support, a better design can be implemented as needed. - if (_akvUrl != DataTestUtility.AKVUrl) + if (string.IsNullOrEmpty(_authority) || string.IsNullOrEmpty(_resource)) { using (HttpClient httpClient = new HttpClient()) { - HttpResponseMessage response = await httpClient.GetAsync(DataTestUtility.AKVUrl); + string akvUrl = new Uri(DataTestUtility.AKVBaseUri, $"/keys/{AKVKeyName}").AbsoluteUri; + HttpResponseMessage response = await httpClient.GetAsync(akvUrl); string challenge = response?.Headers.WwwAuthenticate.FirstOrDefault()?.ToString(); string trimmedChallenge = ValidateChallenge(challenge); string[] pairs = trimmedChallenge.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries); @@ -66,8 +67,6 @@ private async Task AcquireTokenAsync() } } } - // Since this is a test, we only create single-instance temp cache - _akvUrl = DataTestUtility.AKVUrl; } AccessToken accessToken = await AzureActiveDirectoryAuthenticationCallback(_authority, _resource); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Json/SqlJsonTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/Json/SqlJsonTest.cs deleted file mode 100644 index 03ead359f0..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Json/SqlJsonTest.cs +++ /dev/null @@ -1,151 +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.Data.SqlTypes; -using System.Linq; -using System.Text.Json; -using Microsoft.Data.SqlTypes; -using Xunit; - -namespace Microsoft.Data.SqlClient.ManualTesting.Tests.Json -{ - - public class SqlJsonTest - { - [Fact] - public void SqlJsonTest_Null() - { - SqlJson json = new(); - Assert.True(json.IsNull); - Assert.Throws(() => json.Value); - - } - - [Fact] - public void SqlJsonTest_NullString() - { - string nullString = null; - SqlJson json = new(nullString); - Assert.True(json.IsNull); - Assert.Throws(() => json.Value); - } - - [Fact] - public void SqlJsonTest_NullJsonDocument() - { - JsonDocument doc = null; - SqlJson json = new(doc); - Assert.True(json.IsNull); - Assert.Throws(() => json.Value); - } - - [Fact] - public void SqlJsonTest_String() - { - SqlJson json = new("{\"key\":\"value\"}"); - Assert.False(json.IsNull); - Assert.Equal("{\"key\":\"value\"}", json.Value); - } - - [Fact] - public void SqlJsonTest_BadString() - { - Assert.ThrowsAny(()=> new SqlJson("{\"key\":\"value\"")); - } - - [Fact] - public void SqlJsonTest_JsonDocument() - { - JsonDocument doc = GenerateRandomJson(); - SqlJson json = new(doc); - Assert.False(json.IsNull); - - var outputDocument = JsonDocument.Parse(json.Value); - Assert.True(JsonElementsAreEqual(doc.RootElement, outputDocument.RootElement)); - } - - [Fact] - public void SqlJsonTest_NullProperty() - { - SqlJson json = SqlJson.Null; - Assert.True(json.IsNull); - Assert.Throws(() => json.Value); - } - - static JsonDocument GenerateRandomJson() - { - var random = new Random(); - - var jsonObject = new - { - id = random.Next(1, 1000), - name = $"Name{random.Next(1, 100)}", - isActive = random.Next(0, 2) == 1, - createdDate = DateTime.Now.AddDays(-random.Next(1, 100)).ToString("yyyy-MM-ddTHH:mm:ssZ"), - scores = new int[] { random.Next(1, 100), random.Next(1, 100), random.Next(1, 100) }, - details = new - { - age = random.Next(18, 60), - city = $"City{random.Next(1, 100)}" - } - }; - - string jsonString = JsonSerializer.Serialize(jsonObject, new JsonSerializerOptions { WriteIndented = true }); - return JsonDocument.Parse(jsonString); - } - - static bool JsonElementsAreEqual(JsonElement element1, JsonElement element2) - { - if (element1.ValueKind != element2.ValueKind) - return false; - - switch (element1.ValueKind) - { - case JsonValueKind.Object: - { - JsonElement.ObjectEnumerator obj1 = element1.EnumerateObject(); - JsonElement.ObjectEnumerator obj2 = element2.EnumerateObject(); - var dict1 = obj1.ToDictionary(p => p.Name, p => p.Value); - var dict2 = obj2.ToDictionary(p => p.Name, p => p.Value); - - if (dict1.Count != dict2.Count) - return false; - - foreach (var kvp in dict1) - { - if (!dict2.TryGetValue(kvp.Key, out var value2)) - return false; - - if (!JsonElementsAreEqual(kvp.Value, value2)) - return false; - } - - return true; - } - case JsonValueKind.Array: - { - var array1 = element1.EnumerateArray(); - var array2 = element2.EnumerateArray(); - - if (array1.Count() != array2.Count()) - return false; - - return array1.Zip(array2, (e1, e2) => JsonElementsAreEqual(e1, e2)).All(equal => equal); - } - case JsonValueKind.String: - return element1.GetString() == element2.GetString(); - case JsonValueKind.Number: - return element1.GetDecimal() == element2.GetDecimal(); - case JsonValueKind.True: - case JsonValueKind.False: - return element1.GetBoolean() == element2.GetBoolean(); - case JsonValueKind.Null: - return true; - default: - throw new NotSupportedException($"Unsupported JsonValueKind: {element1.ValueKind}"); - } - } - } -} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj index a5b217f1cd..0da63efa61 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj @@ -30,9 +30,8 @@ - - + @@ -49,6 +48,7 @@ + @@ -145,8 +145,10 @@ + + @@ -176,12 +178,16 @@ + + + + @@ -200,7 +206,7 @@ - + @@ -210,6 +216,9 @@ + + + @@ -243,18 +252,15 @@ PreserveNewest data.xml - - PreserveNewest - MultipleResultsTest.bsl - - + + + - @@ -281,7 +287,6 @@ - @@ -295,14 +300,14 @@ - - - + + Common + Address @@ -322,49 +327,48 @@ - - - - - + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - + - + - - - - - - - - + + + + + + + - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + PreserveNewest %(Filename)%(Extension) + + + PreserveNewest diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/MultipleResultsTest/MultipleResultsTest.bsl b/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/MultipleResultsTest/MultipleResultsTest.bsl deleted file mode 100644 index 99bde8b2c0..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/MultipleResultsTest/MultipleResultsTest.bsl +++ /dev/null @@ -1,47 +0,0 @@ -MultipleErrorHandling SqlConnection -**** ExecuteNonQuery ***** -SqlException: Error 1 -Error 2 -Error 3 -Error 4 -Error 5 -0 -1 -3 -4 -5 -6 -7 -8 -9 -10 -11 -**** ExecuteScalar **** -*** SQL CONNECTION INFO MESSAGE : 0 **** -**** ExecuteReader **** -*** SQL CONNECTION INFO MESSAGE : 0 **** -NextResult -*** SQL CONNECTION INFO MESSAGE : 1 **** -SqlException: Error 1 -NextResult -*** SQL CONNECTION INFO MESSAGE : 3 **** -NextResult -*** SQL CONNECTION INFO MESSAGE : 4 **** -SqlException: Error 2 -NextResult -*** SQL CONNECTION INFO MESSAGE : 5 **** -NextResult -*** SQL CONNECTION INFO MESSAGE : 6 **** -SqlException: Error 3 -NextResult -*** SQL CONNECTION INFO MESSAGE : 7 **** -NextResult -*** SQL CONNECTION INFO MESSAGE : 8 **** -SqlException: Error 4 -NextResult -*** SQL CONNECTION INFO MESSAGE : 9 **** -NextResult -*** SQL CONNECTION INFO MESSAGE : 10 **** -SqlException: Error 5 -NextResult -*** SQL CONNECTION INFO MESSAGE : 11 **** diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/MultipleResultsTest/MultipleResultsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/MultipleResultsTest/MultipleResultsTest.cs index cc5d6b42c2..199b93e15f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/MultipleResultsTest/MultipleResultsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/MultipleResultsTest/MultipleResultsTest.cs @@ -3,365 +3,160 @@ // See the LICENSE file in the project root for more information. using System; -using System.Data.Common; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; +using System.Collections.Concurrent; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { public class MultipleResultsTest { - private StringBuilder _globalBuilder = new StringBuilder(); - private StringBuilder _outputBuilder; - private string[] _outputFilter; + private const string ResultSet1_Message = "0"; + private const string ResultSet2_Message = "1"; + private const string ResultSet2_Error = "Error 1"; + + private const string ResultSet3_Message = "3"; + private const string ResultSet4_Message = "4"; + private const string ResultSet4_Error = "Error 2"; + + private const string ResultSet5_Message = "5"; + private const string ResultSet6_Message = "6"; + private const string ResultSet6_Error = "Error 3"; + + private const string ResultSet7_Message = "7"; + private const string ResultSet8_Message = "8"; + private const string ResultSet8_Error = "Error 4"; + + private const string ResultSet9_Message = "9"; + private const string ResultSet10_Message = "10"; + private const string ResultSet10_Error = "Error 5"; + + private const string ResultSet11_Message = "11"; + + private readonly static string s_sqlStatement = + $"PRINT N'{ResultSet1_Message}'; SELECT num = 1, str = 'ABC';\n" + + $"PRINT N'{ResultSet2_Message}'; RAISERROR('{ResultSet2_Error}', 15, 1);\n" + + $"PRINT N'{ResultSet3_Message}'; SELECT num = 2, str = 'ABC';\n" + + $"PRINT N'{ResultSet4_Message}'; RAISERROR('{ResultSet4_Error}', 15, 1);\n" + + $"PRINT N'{ResultSet5_Message}'; SELECT num = 3, str = 'ABC';\n" + + $"PRINT N'{ResultSet6_Message}'; RAISERROR('{ResultSet6_Error}', 15, 1);\n" + + $"PRINT N'{ResultSet7_Message}'; SELECT num = 4, str = 'ABC';\n" + + $"PRINT N'{ResultSet8_Message}'; RAISERROR('{ResultSet8_Error}', 15, 1);\n" + + $"PRINT N'{ResultSet9_Message}'; SELECT num = 5, str = 'ABC';\n" + + $"PRINT N'{ResultSet10_Message}'; RAISERROR('{ResultSet10_Error}', 15, 1);\n" + + $"PRINT N'{ResultSet11_Message}';"; [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - public void TestMain() + public void ExecuteNonQuery() { - Assert.True(RunTestCoreAndCompareWithBaseline()); - } + using SqlConnection connection = new SqlConnection((new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { MultipleActiveResultSets = true }).ConnectionString); + using SqlCommand command = connection.CreateCommand(); + ConcurrentQueue messages = new ConcurrentQueue(); - private void RunTest() - { - MultipleErrorHandling(new SqlConnection((new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { MultipleActiveResultSets = true }).ConnectionString)); - } + connection.InfoMessage += (object sender, SqlInfoMessageEventArgs args) => + messages.Enqueue(args.Message); - private void MultipleErrorHandling(DbConnection connection) - { - try - { - Console.WriteLine("MultipleErrorHandling {0}", connection.GetType().Name); - Type expectedException = null; - if (connection is SqlConnection) - { - ((SqlConnection)connection).InfoMessage += delegate (object sender, SqlInfoMessageEventArgs args) - { - Console.WriteLine("*** SQL CONNECTION INFO MESSAGE : {0} ****", args.Message); - }; - expectedException = typeof(SqlException); - } - connection.Open(); - - using (DbCommand command = connection.CreateCommand()) - { - command.CommandText = - "PRINT N'0';\n" + - "SELECT num = 1, str = 'ABC';\n" + - "PRINT N'1';\n" + - "RAISERROR('Error 1', 15, 1);\n" + - "PRINT N'3';\n" + - "SELECT num = 2, str = 'ABC';\n" + - "PRINT N'4';\n" + - "RAISERROR('Error 2', 15, 1);\n" + - "PRINT N'5';\n" + - "SELECT num = 3, str = 'ABC';\n" + - "PRINT N'6';\n" + - "RAISERROR('Error 3', 15, 1);\n" + - "PRINT N'7';\n" + - "SELECT num = 4, str = 'ABC';\n" + - "PRINT N'8';\n" + - "RAISERROR('Error 4', 15, 1);\n" + - "PRINT N'9';\n" + - "SELECT num = 5, str = 'ABC';\n" + - "PRINT N'10';\n" + - "RAISERROR('Error 5', 15, 1);\n" + - "PRINT N'11';\n"; - - try - { - Console.WriteLine("**** ExecuteNonQuery *****"); - command.ExecuteNonQuery(); - } - catch (Exception e) - { - PrintException(expectedException, e); - } - - try - { - Console.WriteLine("**** ExecuteScalar ****"); - command.ExecuteScalar(); - } - catch (Exception e) - { - PrintException(expectedException, e); - } - - try - { - Console.WriteLine("**** ExecuteReader ****"); - using (DbDataReader reader = command.ExecuteReader()) - { - bool moreResults = true; - do - { - try - { - Console.WriteLine("NextResult"); - moreResults = reader.NextResult(); - } - catch (Exception e) - { - PrintException(expectedException, e); - } - } while (moreResults); - } - } - catch (Exception e) - { - PrintException(null, e); - } - } - } - catch (Exception e) - { - PrintException(null, e); - } - try - { - connection.Dispose(); - } - catch (Exception e) - { - PrintException(null, e); - } - } + connection.Open(); - private bool RunTestCoreAndCompareWithBaseline() - { - string outputPath = "MultipleResultsTest.out"; - string baselinePath = "MultipleResultsTest.bsl"; - - var fstream = new FileStream(outputPath, FileMode.Create, FileAccess.Write, FileShare.Read); - var swriter = new StreamWriter(fstream, Encoding.UTF8); - // Convert all string writes of '\n' to '\r\n' so output files can be 'text' not 'binary' - var twriter = new CarriageReturnLineFeedReplacer(swriter); - Console.SetOut(twriter); // "redirect" Console.Out - - // Run Test - RunTest(); - - Console.Out.Flush(); - Console.Out.Dispose(); - - // Recover the standard output stream - StreamWriter standardOutput = new StreamWriter(Console.OpenStandardOutput()); - standardOutput.AutoFlush = true; - Console.SetOut(standardOutput); - - // Compare output file - var comparisonResult = FindDiffFromBaseline(baselinePath, outputPath); - - if (string.IsNullOrEmpty(comparisonResult)) - { - return true; - } - - Console.WriteLine("Test Failed!"); - Console.WriteLine("Please compare baseline : {0} with output :{1}", Path.GetFullPath(baselinePath), Path.GetFullPath(outputPath)); - Console.WriteLine("Comparison Results : "); - Console.WriteLine(comparisonResult); - return false; - } + command.CommandText = s_sqlStatement; - private void PrintException(Type expected, Exception e, params string[] values) - { - try - { - Debug.Assert(e != null, "PrintException: null exception"); - - _globalBuilder.Length = 0; - _globalBuilder.Append(e.GetType().Name).Append(": "); - - if (e is COMException) - { - _globalBuilder.Append("0x").Append((((COMException)e).HResult).ToString("X8")); - if (expected != e.GetType()) - { - _globalBuilder.Append(": ").Append(e.ToString()); - } - } - else - { - _globalBuilder.Append(e.Message); - } - AssemblyFilter(_globalBuilder); - Console.WriteLine(_globalBuilder.ToString()); - - if (expected != e.GetType()) - { - Console.WriteLine(e.StackTrace); - } - if (values != null) - { - foreach (string value in values) - { - Console.WriteLine(value); - } - } - if (e.InnerException != null) - { - PrintException(e.InnerException.GetType(), e.InnerException); - } - Console.Out.Flush(); - } - catch (Exception f) - { - Console.WriteLine(f); - } - } + // ExecuteNonQuery will drain every result set, info message and exception, collating these into a single exception. + Func testCode = () => command.ExecuteNonQuery(); + SqlException exNonQuery = Assert.Throws(testCode); - private string FindDiffFromBaseline(string baselinePath, string outputPath) - { - var expectedLines = File.ReadAllLines(baselinePath); - var outputLines = File.ReadAllLines(outputPath); - - var comparisonSb = new StringBuilder(); - - // Start compare results - var expectedLength = expectedLines.Length; - var outputLength = outputLines.Length; - var findDiffLength = Math.Min(expectedLength, outputLength); - - // Find diff for each lines - for (var lineNo = 0; lineNo < findDiffLength; lineNo++) - { - if (!expectedLines[lineNo].Equals(outputLines[lineNo])) - { - comparisonSb.AppendFormat("** DIFF at line {0} \n", lineNo); - comparisonSb.AppendFormat("A : {0} \n", outputLines[lineNo]); - comparisonSb.AppendFormat("E : {0} \n", expectedLines[lineNo]); - } - } - - var startIndex = findDiffLength - 1; - if (startIndex < 0) - startIndex = 0; - - if (findDiffLength < expectedLength) - { - comparisonSb.AppendFormat("** MISSING \n"); - for (var lineNo = startIndex; lineNo < expectedLength; lineNo++) - { - comparisonSb.AppendFormat("{0} : {1}", lineNo, expectedLines[lineNo]); - } - } - if (findDiffLength < outputLength) - { - comparisonSb.AppendFormat("** EXTRA \n"); - for (var lineNo = startIndex; lineNo < outputLength; lineNo++) - { - comparisonSb.AppendFormat("{0} : {1}", lineNo, outputLines[lineNo]); - } - } - - return comparisonSb.ToString(); + string expectedInfoMessages = string.Join(Environment.NewLine, + ResultSet2_Error, ResultSet4_Error, ResultSet6_Error, ResultSet8_Error, ResultSet10_Error, + ResultSet1_Message, ResultSet2_Message, ResultSet3_Message, ResultSet4_Message, ResultSet5_Message, + ResultSet6_Message, ResultSet7_Message, ResultSet8_Message, ResultSet9_Message, ResultSet10_Message, + ResultSet11_Message); + + Assert.Equal(expectedInfoMessages, exNonQuery.Message); + Assert.Empty(messages); } - private string AssemblyFilter(StreamWriter writer) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public void ExecuteScalar() { - if (_outputBuilder == null) - { - _outputBuilder = new StringBuilder(); - } - _outputBuilder.Length = 0; - - byte[] utf8 = ((MemoryStream)writer.BaseStream).ToArray(); - string value = System.Text.Encoding.UTF8.GetString(utf8, 3, utf8.Length - 3); // skip 0xEF, 0xBB, 0xBF - _outputBuilder.Append(value); - AssemblyFilter(_outputBuilder); - return _outputBuilder.ToString(); + using SqlConnection connection = new SqlConnection((new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { MultipleActiveResultSets = true }).ConnectionString); + using SqlCommand command = connection.CreateCommand(); + ConcurrentQueue messages = new ConcurrentQueue(); + + connection.InfoMessage += (object sender, SqlInfoMessageEventArgs args) => + messages.Enqueue(args.Message); + + connection.Open(); + + command.CommandText = s_sqlStatement; + + // ExecuteScalar will select the first result set and the info message preceding it, then stop. + command.ExecuteScalar(); + Assert.True(messages.TryDequeue(out string lastMessage)); + Assert.Empty(messages); + Assert.Equal(ResultSet1_Message, lastMessage); } - private void AssemblyFilter(StringBuilder builder) + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public void ExecuteReader() { - string[] filter = _outputFilter; - if (filter == null) - { - filter = new string[5]; - string tmp = typeof(System.Guid).AssemblyQualifiedName; - filter[0] = tmp.Substring(tmp.IndexOf(',')); - filter[1] = filter[0].Replace("mscorlib", "System"); - filter[2] = filter[0].Replace("mscorlib", "System.Data"); - filter[3] = filter[0].Replace("mscorlib", "System.Data.OracleClient"); - filter[4] = filter[0].Replace("mscorlib", "System.Xml"); - _outputFilter = filter; - } - - for (int i = 0; i < filter.Length; ++i) - { - builder.Replace(filter[i], ""); - } + using SqlConnection connection = new SqlConnection((new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { MultipleActiveResultSets = true }).ConnectionString); + using SqlCommand command = connection.CreateCommand(); + ConcurrentQueue messages = new ConcurrentQueue(); + + connection.InfoMessage += (object sender, SqlInfoMessageEventArgs args) => + messages.Enqueue(args.Message); + + connection.Open(); + + command.CommandText = s_sqlStatement; + + // SqlDataReader will drain every result set it's told to, throwing exceptions and printing messages as it goes. + using SqlDataReader reader = command.ExecuteReader(); + + // Result set 1: a result set and a message of '0'. + // Result set 2: a message of '1' and an exception. + AdvanceReader(reader, messages, ResultSet1_Message, ResultSet2_Message, ResultSet2_Error, finalBlock: false); + + // Result set 3: a result set and a message of '3'. + // Result set 4: a message of '4' and an exception. + AdvanceReader(reader, messages, ResultSet3_Message, ResultSet4_Message, ResultSet4_Error, finalBlock: false); + + // Result set 5: a result set and a message of '5'. + // Result set 6: a message of '6' and an exception. + AdvanceReader(reader, messages, ResultSet5_Message, ResultSet6_Message, ResultSet6_Error, finalBlock: false); + + // Result set 7: a result set and a message of '7'. + // Result set 8: a message of '8' and an exception. + AdvanceReader(reader, messages, ResultSet7_Message, ResultSet8_Message, ResultSet8_Error, finalBlock: false); + + // Result set 9: a result set and a message of '9'. + // Result set 10: a message of '10' and an exception. + AdvanceReader(reader, messages, ResultSet9_Message, ResultSet10_Message, ResultSet10_Error, finalBlock: true); + + // One message following the final result set + Assert.True(messages.TryDequeue(out string lastMessage)); + Assert.Empty(messages); + Assert.Equal(ResultSet11_Message, lastMessage); } - /// - /// special wrapper for the text writer to replace single "\n" with "\n" - /// - private sealed class CarriageReturnLineFeedReplacer : TextWriter + private static void AdvanceReader(SqlDataReader reader, ConcurrentQueue messageBuffer, string resultSet1Message, string resultSet2Message, string resultSet2ExceptionMessage, bool finalBlock) { - private TextWriter _output; - private int _lineFeedCount; - private bool _hasCarriageReturn; - - internal CarriageReturnLineFeedReplacer(TextWriter output) - { - if (output == null) - throw new ArgumentNullException(nameof(output)); - - _output = output; - } - - public int LineFeedCount - { - get { return _lineFeedCount; } - } - - public override Encoding Encoding - { - get { return _output.Encoding; } - } - - public override IFormatProvider FormatProvider - { - get { return _output.FormatProvider; } - } - - public override string NewLine - { - get { return _output.NewLine; } - set { _output.NewLine = value; } - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - ((IDisposable)_output).Dispose(); - } - _output = null; - } - - public override void Flush() - { - _output.Flush(); - } - - public override void Write(char value) - { - if ('\n' == value) - { - _lineFeedCount++; - if (!_hasCarriageReturn) - { // X'\n'Y -> X'\r\n'Y - _output.Write('\r'); - } - } - _hasCarriageReturn = '\r' == value; - _output.Write(value); - } + bool moreResults = true; + + // This is a pair of result sets: + // Result set 1: an info message of something and a result set + // Result set 2: an info message of something and an exception + Assert.True(messageBuffer.TryDequeue(out string lastMessage)); + Assert.Empty(messageBuffer); + Assert.Equal(resultSet1Message, lastMessage); + + SqlException exReader = Assert.Throws(() => moreResults = reader.NextResult()); + Assert.Equal(resultSet2ExceptionMessage, exReader.Message); + + Assert.True(messageBuffer.TryDequeue(out lastMessage)); + Assert.Empty(messageBuffer); + Assert.Equal(resultSet2Message, lastMessage); + + moreResults = reader.NextResult(); + Assert.Equal(finalBlock, !moreResults); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AADFedAuthTokenRefreshTest/AADFedAuthTokenRefreshTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AADFedAuthTokenRefreshTest/AADFedAuthTokenRefreshTest.cs index 875d755a59..027acfde23 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AADFedAuthTokenRefreshTest/AADFedAuthTokenRefreshTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AADFedAuthTokenRefreshTest/AADFedAuthTokenRefreshTest.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Diagnostics; using Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.Common.SystemDataInternals; using Xunit; using Xunit.Abstractions; @@ -66,6 +67,7 @@ public void FedAuthTokenRefreshTest() } } + [Conditional("DEBUG")] private void LogInfo(string message) { _testOutputHelper.WriteLine(message); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.cs index c02658284b..a883eb9b88 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Batch/BatchTests.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Threading.Tasks; @@ -24,6 +25,17 @@ public static void MissingCommandTextThrows() } } + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static async Task MissingCommandsThrows() + { + using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + using (var batch = new SqlBatch { Connection = connection }) + { + connection.Open(); + await Assert.ThrowsAsync(() => batch.ExecuteReaderAsync()); + } + } + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] public static void MissingConnectionThrows() { @@ -74,9 +86,13 @@ public static void SqlBatchCanCreateParameter() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] public static void StoredProcedureBatchSupported() { + SqlRetryLogicOption rto = new() { NumberOfTries = 3, DeltaTime = TimeSpan.FromMilliseconds(100), TransientErrors = new[] { 1205 } }; // Retry on 1205 / Deadlock + SqlRetryLogicBaseProvider prov = SqlConfigurableRetryFactory.CreateIncrementalRetryProvider(rto); + using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) - using (var batch = new SqlBatch { Connection = connection, BatchCommands = { new SqlBatchCommand("sp_help", CommandType.StoredProcedure) } }) + using (var batch = new SqlBatch { Connection = connection, BatchCommands = { new SqlBatchCommand("sp_help", CommandType.StoredProcedure, new List { new("@objname", "sys.indexes") }) } }) { + connection.RetryLogicProvider = prov; connection.Open(); batch.ExecuteNonQuery(); } @@ -102,19 +118,24 @@ public static void TableDirectBatchNotSupported() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] public static void MixedBatchSupported() { + SqlRetryLogicOption rto = new() { NumberOfTries = 3, DeltaTime = TimeSpan.FromMilliseconds(100), TransientErrors = new[] { 1205 } }; // Retry on 1205 / Deadlock + SqlRetryLogicBaseProvider prov = SqlConfigurableRetryFactory.CreateIncrementalRetryProvider(rto); + using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) using (var batch = new SqlBatch + { + Connection = connection, + BatchCommands = + { + new SqlBatchCommand("select @@SPID", CommandType.Text), + new SqlBatchCommand("sp_help", CommandType.StoredProcedure, new List { new("@objname", "sys.indexes") }) + } + }) { - Connection = connection, - BatchCommands = - { - new SqlBatchCommand("select @@SPID", CommandType.Text), - new SqlBatchCommand("sp_help",CommandType.StoredProcedure) - } - }) - { + connection.RetryLogicProvider = prov; connection.Open(); batch.ExecuteNonQuery(); + return; } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/InternalConnectionWrapper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/InternalConnectionWrapper.cs index 7a49860f45..4086c73265 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/InternalConnectionWrapper.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/InternalConnectionWrapper.cs @@ -126,20 +126,25 @@ public void KillConnection() { object tdsParser = ConnectionHelper.GetParser(_internalConnection); object stateObject = TdsParserHelper.GetStateObject(tdsParser); - object sessionHandle = TdsParserStateObjectHelper.GetSessionHandle(stateObject); - Assembly systemDotData = Assembly.Load(new AssemblyName(typeof(SqlConnection).GetTypeInfo().Assembly.FullName)); - Type sniHandleType = systemDotData.GetType("Microsoft.Data.SqlClient.SNI.SNIHandle"); - MethodInfo killConn = sniHandleType.GetMethod("KillConnection"); + Assembly assembly = Assembly.Load(new AssemblyName(typeof(SqlConnection).GetTypeInfo().Assembly.FullName)); + Type sniHandleType = assembly.GetType("Microsoft.Data.SqlClient.ManagedSni.SniHandle"); - if (killConn != null) + MethodInfo killConn = null; + if (sniHandleType is not null) { - killConn.Invoke(sessionHandle, null); + killConn = sniHandleType.GetMethod("KillConnection"); } - else + + if (killConn is null) { throw new InvalidOperationException("Error: Could not find SNI KillConnection test hook. This operation is only supported in debug builds."); } + + killConn.Invoke( + TdsParserStateObjectHelper.GetSessionHandle(stateObject), + null); + // Ensure kill occurs outside of check connection window Thread.Sleep(100); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs index d7c5471427..6c828b188b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/ConnectionPoolHelper.cs @@ -14,23 +14,24 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SystemDataInternals internal static class ConnectionPoolHelper { private static Assembly s_MicrosoftDotData = Assembly.Load(new AssemblyName(typeof(SqlConnection).GetTypeInfo().Assembly.FullName)); - private static Type s_dbConnectionPool = s_MicrosoftDotData.GetType("Microsoft.Data.ProviderBase.DbConnectionPool"); - private static Type s_dbConnectionPoolGroup = s_MicrosoftDotData.GetType("Microsoft.Data.ProviderBase.DbConnectionPoolGroup"); - private static Type s_dbConnectionPoolIdentity = s_MicrosoftDotData.GetType("Microsoft.Data.ProviderBase.DbConnectionPoolIdentity"); + private static Type s_dbConnectionPool = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.ConnectionPool.IDbConnectionPool"); + private static Type s_waitHandleDbConnectionPool = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.ConnectionPool.WaitHandleDbConnectionPool"); + private static Type s_dbConnectionPoolGroup = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.ConnectionPool.DbConnectionPoolGroup"); + private static Type s_dbConnectionPoolIdentity = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.ConnectionPool.DbConnectionPoolIdentity"); private static Type s_dbConnectionFactory = s_MicrosoftDotData.GetType("Microsoft.Data.ProviderBase.DbConnectionFactory"); private static Type s_sqlConnectionFactory = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.SqlConnectionFactory"); - private static Type s_dbConnectionPoolKey = s_MicrosoftDotData.GetType("Microsoft.Data.Common.DbConnectionPoolKey"); + private static Type s_dbConnectionPoolKey = s_MicrosoftDotData.GetType("Microsoft.Data.SqlClient.ConnectionPool.DbConnectionPoolKey"); private static Type s_dictStringPoolGroup = typeof(Dictionary<,>).MakeGenericType(s_dbConnectionPoolKey, s_dbConnectionPoolGroup); private static Type s_dictPoolIdentityPool = typeof(ConcurrentDictionary<,>).MakeGenericType(s_dbConnectionPoolIdentity, s_dbConnectionPool); - private static PropertyInfo s_dbConnectionPoolCount = s_dbConnectionPool.GetProperty("Count", BindingFlags.Instance | BindingFlags.NonPublic); + private static PropertyInfo s_dbConnectionPoolCount = s_waitHandleDbConnectionPool.GetProperty("Count", BindingFlags.Instance | BindingFlags.Public); private static PropertyInfo s_dictStringPoolGroupGetKeys = s_dictStringPoolGroup.GetProperty("Keys"); private static PropertyInfo s_dictPoolIdentityPoolValues = s_dictPoolIdentityPool.GetProperty("Values"); private static FieldInfo s_dbConnectionFactoryPoolGroupList = s_dbConnectionFactory.GetField("_connectionPoolGroups", BindingFlags.Instance | BindingFlags.NonPublic); private static FieldInfo s_dbConnectionPoolGroupPoolCollection = s_dbConnectionPoolGroup.GetField("_poolCollection", BindingFlags.Instance | BindingFlags.NonPublic); private static FieldInfo s_sqlConnectionFactorySingleton = s_sqlConnectionFactory.GetField("SingletonInstance", BindingFlags.Static | BindingFlags.Public); - private static FieldInfo s_dbConnectionPoolStackOld = s_dbConnectionPool.GetField("_stackOld", BindingFlags.Instance | BindingFlags.NonPublic); - private static FieldInfo s_dbConnectionPoolStackNew = s_dbConnectionPool.GetField("_stackNew", BindingFlags.Instance | BindingFlags.NonPublic); - private static MethodInfo s_dbConnectionPoolCleanup = s_dbConnectionPool.GetMethod("CleanupCallback", BindingFlags.Instance | BindingFlags.NonPublic); + private static FieldInfo s_dbConnectionPoolStackOld = s_waitHandleDbConnectionPool.GetField("_stackOld", BindingFlags.Instance | BindingFlags.NonPublic); + private static FieldInfo s_dbConnectionPoolStackNew = s_waitHandleDbConnectionPool.GetField("_stackNew", BindingFlags.Instance | BindingFlags.NonPublic); + private static MethodInfo s_dbConnectionPoolCleanup = s_waitHandleDbConnectionPool.GetMethod("CleanupCallback", BindingFlags.Instance | BindingFlags.NonPublic); private static MethodInfo s_dictStringPoolGroupTryGetValue = s_dictStringPoolGroup.GetMethod("TryGetValue"); public static int CountFreeConnections(object pool) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/FedAuthTokenHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/FedAuthTokenHelper.cs index 26d2477b9b..a0a3bc4498 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/FedAuthTokenHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/FedAuthTokenHelper.cs @@ -96,7 +96,7 @@ internal static object GetAuthenticationContextValue(SqlConnection connection) object databaseConnectionPoolObj = innerConnectionObj.GetType().GetProperty("Pool", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(innerConnectionObj); - IEnumerable authenticationContexts = (IEnumerable)databaseConnectionPoolObj.GetType().GetProperty("AuthenticationContexts", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(databaseConnectionPoolObj, null); + IEnumerable authenticationContexts = (IEnumerable)databaseConnectionPoolObj.GetType().GetProperty("AuthenticationContexts", BindingFlags.Public | BindingFlags.Instance).GetValue(databaseConnectionPoolObj, null); object authenticationContextObj = authenticationContexts.Cast().FirstOrDefault(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserStateObjectHelper.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserStateObjectHelper.cs index 2d6e234f55..cd20701f74 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserStateObjectHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/Common/SystemDataInternals/TdsParserStateObjectHelper.cs @@ -4,20 +4,61 @@ using System; using System.Reflection; +using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SystemDataInternals { internal static class TdsParserStateObjectHelper { - private static readonly Assembly s_systemDotData = typeof(Microsoft.Data.SqlClient.SqlConnection).GetTypeInfo().Assembly; - private static readonly Type s_tdsParserStateObject = s_systemDotData.GetType("Microsoft.Data.SqlClient.TdsParserStateObject"); - private static readonly FieldInfo s_forceAllPends = s_tdsParserStateObject.GetField("s_forceAllPends", BindingFlags.Static | BindingFlags.NonPublic); - private static readonly FieldInfo s_skipSendAttention = s_tdsParserStateObject.GetField("s_skipSendAttention", BindingFlags.Static | BindingFlags.NonPublic); - private static readonly FieldInfo s_forceSyncOverAsyncAfterFirstPend = s_tdsParserStateObject.GetField("s_forceSyncOverAsyncAfterFirstPend", BindingFlags.Static | BindingFlags.NonPublic); - private static readonly FieldInfo s_failAsyncPends = s_tdsParserStateObject.GetField("s_failAsyncPends", BindingFlags.Static | BindingFlags.NonPublic); - private static readonly FieldInfo s_forcePendingReadsToWaitForUser = s_tdsParserStateObject.GetField("s_forcePendingReadsToWaitForUser", BindingFlags.Static | BindingFlags.NonPublic); - private static readonly Type s_tdsParserStateObjectManaged = s_systemDotData.GetType("Microsoft.Data.SqlClient.SNI.TdsParserStateObjectManaged"); - private static readonly FieldInfo s_tdsParserStateObjectManagedSessionHandle = s_tdsParserStateObjectManaged.GetField("_sessionHandle", BindingFlags.Instance | BindingFlags.NonPublic); + private static readonly FieldInfo s_forceAllPends; + private static readonly FieldInfo s_skipSendAttention; + private static readonly FieldInfo s_forceSyncOverAsyncAfterFirstPend; + private static readonly FieldInfo s_failAsyncPends; + private static readonly FieldInfo s_forcePendingReadsToWaitForUser; + private static readonly Type s_tdsParserStateObjectManaged; + private static readonly FieldInfo s_tdsParserStateObjectManagedSessionHandle; + + static TdsParserStateObjectHelper() + { + Assembly assembly = typeof(Microsoft.Data.SqlClient.SqlConnection).GetTypeInfo().Assembly; + Assert.True(assembly is not null, nameof(assembly)); + + Type tdsParserStateObject = assembly.GetType("Microsoft.Data.SqlClient.TdsParserStateObject"); + Assert.True(tdsParserStateObject is not null, nameof(tdsParserStateObject)); + + s_forceAllPends = tdsParserStateObject.GetField("s_forceAllPends", BindingFlags.Static | BindingFlags.NonPublic); + Assert.True(s_forceAllPends is not null, nameof(s_forceAllPends)); + + s_skipSendAttention = tdsParserStateObject.GetField("s_skipSendAttention", BindingFlags.Static | BindingFlags.NonPublic); + Assert.True(s_skipSendAttention is not null, nameof(s_skipSendAttention)); + + s_forceSyncOverAsyncAfterFirstPend = tdsParserStateObject.GetField("s_forceSyncOverAsyncAfterFirstPend", BindingFlags.Static | BindingFlags.NonPublic); + Assert.True(s_forceSyncOverAsyncAfterFirstPend is not null, nameof(s_forceSyncOverAsyncAfterFirstPend)); + + s_failAsyncPends = tdsParserStateObject.GetField("s_failAsyncPends", BindingFlags.Static | BindingFlags.NonPublic); + Assert.True(s_failAsyncPends is not null, nameof(s_failAsyncPends)); + + s_forcePendingReadsToWaitForUser = tdsParserStateObject.GetField("s_forcePendingReadsToWaitForUser", BindingFlags.Static | BindingFlags.NonPublic); + Assert.True(s_forcePendingReadsToWaitForUser is not null, nameof(s_forcePendingReadsToWaitForUser)); + + // These managed SNI handles are allowed to be null, since they + // won't exist in .NET Framework builds. + s_tdsParserStateObjectManaged = + assembly.GetType("Microsoft.Data.SqlClient.ManagedSni.TdsParserStateObjectManaged"); + s_tdsParserStateObjectManagedSessionHandle = null; + if (s_tdsParserStateObjectManaged is not null) + { + s_tdsParserStateObjectManagedSessionHandle = + s_tdsParserStateObjectManaged.GetField( + "_sessionHandle", + BindingFlags.Instance | BindingFlags.NonPublic); + // If we have the managed SNI type, we must have the session + // handle field. + Assert.True( + s_tdsParserStateObjectManagedSessionHandle is not null, + nameof(s_tdsParserStateObjectManagedSessionHandle)); + } + } internal static bool ForceAllPends { @@ -49,17 +90,23 @@ internal static bool FailAsyncPends set { s_failAsyncPends.SetValue(null, value); } } - private static void VerifyObjectIsTdsParserStateObject(object stateObject) + internal static object GetSessionHandle(object stateObject) { if (stateObject == null) + { throw new ArgumentNullException(nameof(stateObject)); + } + + if (s_tdsParserStateObjectManaged is null) + { + throw new ArgumentException("Library being tested does not implement TdsParserStateObjectManaged", nameof(stateObject)); + } + if (!s_tdsParserStateObjectManaged.IsInstanceOfType(stateObject)) + { throw new ArgumentException("Object provided was not a TdsParserStateObjectManaged", nameof(stateObject)); - } + } - internal static object GetSessionHandle(object stateObject) - { - VerifyObjectIsTdsParserStateObject(stateObject); return s_tdsParserStateObjectManagedSessionHandle.GetValue(stateObject); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs index 8003660889..160f7f550e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConfigurableIpPreferenceTest/ConfigurableIpPreferenceTest.cs @@ -9,6 +9,7 @@ using System.Net; using System.Net.Sockets; using System.Reflection; +using System.Runtime.InteropServices; using Microsoft.Data.SqlClient.ManualTesting.Tests.SystemDataInternals; using Xunit; @@ -21,6 +22,7 @@ public class ConfigurableIpPreferenceTest { private const string CnnPrefIPv6 = ";IPAddressPreference=IPv6First"; private const string CnnPrefIPv4 = ";IPAddressPreference=IPv4First"; + private const string LocalHost = "localhost"; private static bool IsTCPConnectionStringSetup() => !string.IsNullOrEmpty(TCPConnectionString); private static bool IsValidDataSource() @@ -37,7 +39,14 @@ private static bool IsValidDataSource() { serverName = builder.DataSource.Substring(startIdx, endIdx - startIdx); } - +#if NET + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && + LocalHost.Equals(serverName, StringComparison.OrdinalIgnoreCase)) + { + // Skip the test for localhost on macOS as Docker (hosting SQL Server) doesn't support IPv6 + return false; + } +#endif List ipAddresses = Dns.GetHostAddresses(serverName).ToList(); return ipAddresses.Exists(ip => ip.AddressFamily == AddressFamily.InterNetwork) && ipAddresses.Exists(ip => ip.AddressFamily == AddressFamily.InterNetworkV6); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/PoolBlockPeriodTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/PoolBlockPeriodTest.cs index 7124d33766..5559dbff8e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/PoolBlockPeriodTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionPoolTest/PoolBlockPeriodTest.cs @@ -13,6 +13,7 @@ public class PoolBlockPeriodTest private const string AzureChinaEnpointSample = "nonexistent.database.chinacloudapi.cn"; private const string AzureUSGovernmentEndpointSample = "nonexistent.database.usgovcloudapi.net"; private const string AzureGermanEndpointSample = "nonexistent.database.cloudapi.de"; + private const string AzureFabricEndpointSample = "nonexistent.database.fabric.microsoft.com"; private const string AzureEndpointMixedCaseSample = "nonexistent.database.WINDOWS.net"; private const string NonExistentServer = "nonexistentserver"; private const string PolicyKeyword = "PoolBlockingPeriod"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTest.cs index d500391227..1a177c5c39 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTest.cs @@ -166,7 +166,6 @@ public void OpeningConnectionWitHNICTest() } } - [ActiveIssue("31754")] [ConditionalFact(nameof(AreConnStringsSetup), nameof(UseManagedSNIOnWindows), nameof(IsNotAzureServer), nameof(IsLocalHost))] [PlatformSpecific(TestPlatforms.Windows)] public void RemoteCertificateNameMismatchErrorTest() @@ -175,6 +174,7 @@ public void RemoteCertificateNameMismatchErrorTest() { DataSource = GetLocalIpAddress(), Encrypt = SqlConnectionEncryptOption.Mandatory, + TrustServerCertificate = false, HostNameInCertificate = "BadHostName" }; using SqlConnection connection = new(builder.ConnectionString); @@ -183,7 +183,6 @@ public void RemoteCertificateNameMismatchErrorTest() Assert.Equal(20, exception.Class); Assert.IsType(exception.InnerException); Assert.StartsWith("Certificate name mismatch. The provided 'DataSource' or 'HostNameInCertificate' does not match the name in the certificate.", exception.InnerException.Message); - Console.WriteLine(exception.Message); } private static void CreateValidCertificate(string script) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTestWithTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTestWithTdsServer.cs index 72fbc3f63a..48b0c9273e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTestWithTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTestWithTdsServer.cs @@ -92,7 +92,13 @@ private static string ForceEncryptionRegistryPath } [ConditionalTheory(nameof(AreConnStringsSetup), nameof(IsNotAzureServer), nameof(IsLocalHost))] - [MemberData(nameof(ConnectionTestParametersData.GetConnectionTestParameters), MemberType = typeof(ConnectionTestParametersData))] + [MemberData( + nameof(ConnectionTestParametersData.GetConnectionTestParameters), + MemberType = typeof(ConnectionTestParametersData), + // xUnit can't consistently serialize the data for this test, so we + // disable enumeration of the test data to avoid warnings on the + // console. + DisableDiscoveryEnumeration = true)] [PlatformSpecific(TestPlatforms.Windows)] public void BeginWindowsConnectionTest(ConnectionTestParameters connectionTestParameters) { @@ -105,7 +111,10 @@ public void BeginWindowsConnectionTest(ConnectionTestParameters connectionTestPa } [ConditionalTheory(nameof(AreConnStringsSetup), nameof(IsNotAzureServer), nameof(IsLocalHost))] - [MemberData(nameof(ConnectionTestParametersData.GetConnectionTestParameters), MemberType = typeof(ConnectionTestParametersData))] + [MemberData( + nameof(ConnectionTestParametersData.GetConnectionTestParameters), + MemberType = typeof(ConnectionTestParametersData), + DisableDiscoveryEnumeration = true)] [PlatformSpecific(TestPlatforms.Linux)] public void BeginLinuxConnectionTest(ConnectionTestParameters connectionTestParameters) { @@ -122,7 +131,12 @@ private void ConnectionTest(ConnectionTestParameters connectionTestParameters) using TestTdsServer server = TestTdsServer.StartTestServer(enableFedAuth: false, enableLog: false, connectionTimeout: 15, methodName: "", +#if NET9_0_OR_GREATER X509CertificateLoader.LoadPkcs12FromFile(s_fullPathToPfx, "nopassword", X509KeyStorageFlags.UserKeySet), +#else + new X509Certificate2(s_fullPathToPfx, "nopassword", X509KeyStorageFlags.UserKeySet), +#endif + encryptionProtocols: connectionTestParameters.EncryptionProtocols, encryptionType: connectionTestParameters.TdsEncryptionType); builder = new(server.ConnectionString) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs index 43c69919d6..d2f385d672 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/AADConnectionTest.cs @@ -501,8 +501,8 @@ public static void ActiveDirectoryManagedIdentityWithInvalidUserIdMustFail(strin SqlException e = Assert.Throws(() => ConnectAndDisconnect(connStrWithNoCred)); - string expectedMessage = "ManagedIdentityCredential authentication unavailable"; - Assert.Contains(expectedMessage, e.GetBaseException().Message); + string expectedMessage = "[Managed Identity] Authentication unavailable"; + Assert.Contains(expectedMessage, e.GetBaseException().Message, StringComparison.OrdinalIgnoreCase); } [ConditionalFact(nameof(IsAADConnStringsSetup))] diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs index e9e29e5940..10b653967d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs @@ -171,7 +171,7 @@ public class ConnectionWorker : IDisposable private static List s_workerList = new(); private ManualResetEventSlim _doneEvent = new(false); private double _timeElapsed; - private Thread _thread; + private Task _task; private string _connectionString; private int _numOfTry; @@ -180,7 +180,7 @@ public ConnectionWorker(string connectionString, int numOfTry) s_workerList.Add(this); _connectionString = connectionString; _numOfTry = numOfTry; - _thread = new Thread(new ThreadStart(SqlConnectionOpen)); + _task = new Task(SqlConnectionOpen, TaskCreationOptions.LongRunning); } public static List WorkerList => s_workerList; @@ -191,7 +191,7 @@ public static void Start() { foreach (ConnectionWorker w in s_workerList) { - w._thread.Start(); + w._task.Start(); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs index a180c21b3b..cd516df696 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderStreamsTest.cs @@ -17,7 +17,16 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests public static class DataReaderStreamsTest { [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(GetCommandBehavioursAndIsAsync))] + [MemberData( + nameof(GetCommandBehavioursAndIsAsync) +#if NETFRAMEWORK + // .NET Framework puts system enums in something called the Global + // Assembly Cache (GAC), and xUnit refuses to serialize enums that + // live there. So for .NET Framework, we disable enumeration of the + // test data to avoid warnings on the console when running tests. + , DisableDiscoveryEnumeration = true +#endif + )] public static async Task GetFieldValueAsync_OfStream(CommandBehavior behavior, bool isExecuteAsync) { const int PacketSize = 512; // force minimun packet size so that the test data spans multiple packets to test sequential access spanning @@ -53,7 +62,12 @@ public static async Task GetFieldValueAsync_OfStream(CommandBehavior behavior, b } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - [MemberData(nameof(GetCommandBehavioursAndIsAsync))] + [MemberData( + nameof(GetCommandBehavioursAndIsAsync) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public static async Task GetFieldValueAsync_OfXmlReader(CommandBehavior behavior, bool isExecuteAsync) { const int PacketSize = 512; // force minimun packet size so that the test data spans multiple packets to test sequential access spanning @@ -87,7 +101,12 @@ public static async Task GetFieldValueAsync_OfXmlReader(CommandBehavior behavior } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(GetCommandBehavioursAndIsAsync))] + [MemberData( + nameof(GetCommandBehavioursAndIsAsync) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public static async Task GetFieldValueAsync_OfTextReader(CommandBehavior behavior, bool isExecuteAsync) { const int PacketSize = 512; // force minimun packet size so that the test data spans multiple packets to test sequential access spanning @@ -121,7 +140,12 @@ public static async Task GetFieldValueAsync_OfTextReader(CommandBehavior behavio } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(GetCommandBehavioursAndIsAsync))] + [MemberData( + nameof(GetCommandBehavioursAndIsAsync) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public static async Task GetFieldValueAsync_Char_OfTextReader(CommandBehavior behavior, bool isExecuteAsync) { const int PacketSize = 512; // force minimun packet size so that the test data spans multiple packets to test sequential access spanning @@ -156,7 +180,12 @@ public static async Task GetFieldValueAsync_Char_OfTextReader(CommandBehavior be // Synapse: Cannot find data type 'XML'. [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - [MemberData(nameof(GetCommandBehavioursAndIsAsync))] + [MemberData( + nameof(GetCommandBehavioursAndIsAsync) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public static async Task GetFieldValue_OfXmlReader(CommandBehavior behavior, bool isExecuteAsync) { const int PacketSize = 512; // force minimun packet size so that the test data spans multiple packets to test sequential access spanning @@ -190,7 +219,12 @@ public static async Task GetFieldValue_OfXmlReader(CommandBehavior behavior, boo } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(GetCommandBehavioursAndIsAsync))] + [MemberData( + nameof(GetCommandBehavioursAndIsAsync) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public static async Task GetFieldValue_OfStream(CommandBehavior behavior, bool isExecuteAsync) { const int PacketSize = 512; // force minimun packet size so that the test data spans multiple packets to test sequential access spanning @@ -223,7 +257,12 @@ public static async Task GetFieldValue_OfStream(CommandBehavior behavior, bool i } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - [MemberData(nameof(GetCommandBehavioursAndIsAsync))] + [MemberData( + nameof(GetCommandBehavioursAndIsAsync) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public static async Task GetFieldValue_OfTextReader(CommandBehavior behavior, bool isExecuteAsync) { const int PacketSize = 512; // force minimun packet size so that the test data spans multiple packets to test sequential access spanning @@ -257,7 +296,12 @@ public static async Task GetFieldValue_OfTextReader(CommandBehavior behavior, bo } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(GetCommandBehavioursAndIsAsync))] + [MemberData( + nameof(GetCommandBehavioursAndIsAsync) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public static async Task GetStream(CommandBehavior behavior, bool isExecuteAsync) { const int PacketSize = 512; // force minimun packet size so that the test data spans multiple packets to test sequential access spanning @@ -293,7 +337,12 @@ public static async Task GetStream(CommandBehavior behavior, bool isExecuteAsync } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - [MemberData(nameof(GetCommandBehavioursAndIsAsync))] + [MemberData( + nameof(GetCommandBehavioursAndIsAsync) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public static async Task GetXmlReader(CommandBehavior behavior, bool isExecuteAsync) { const int PacketSize = 512; // force minimun packet size so that the test data spans multiple packets to test sequential access spanning @@ -327,7 +376,12 @@ public static async Task GetXmlReader(CommandBehavior behavior, bool isExecuteAs } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - [MemberData(nameof(GetCommandBehavioursAndIsAsync))] + [MemberData( + nameof(GetCommandBehavioursAndIsAsync) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public static async Task GetTextReader(CommandBehavior behavior, bool isExecuteAsync) { const int PacketSize = 512; // force minimun packet size so that the test data spans multiple packets to test sequential access spanning @@ -361,7 +415,12 @@ public static async Task GetTextReader(CommandBehavior behavior, bool isExecuteA } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - [MemberData(nameof(GetCommandBehaviourAndAccessorTypes))] + [MemberData( + nameof(GetCommandBehaviourAndAccessorTypes) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public static void NullStreamProperties(CommandBehavior behavior, AccessorType accessorType) { string query = "SELECT convert(xml,NULL) AS XmlData, convert(nvarchar(max),NULL) as TextData, convert(varbinary(max),NULL) as StreamData"; @@ -446,7 +505,12 @@ public static void NullStreamProperties(CommandBehavior behavior, AccessorType a } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - [MemberData(nameof(GetCommandBehaviourAndAccessorTypes))] + [MemberData( + nameof(GetCommandBehaviourAndAccessorTypes) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] public static void InvalidCastExceptionStream(CommandBehavior behavior, AccessorType accessorType) { string query = "SELECT convert(xml,NULL) AS XmlData, convert(nvarchar(max),NULL) as TextData"; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs index d00ea1d226..5c9a19afcb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs @@ -3,28 +3,25 @@ // See the LICENSE file in the project root for more information. using System; +using System.Buffers; using System.Collections.Generic; using System.Data; using System.Data.SqlTypes; +using System.Linq; using System.Reflection; using System.Text; using System.Threading; +using System.Threading.Tasks; using Xunit; +using SwitchesHelper = Microsoft.Data.SqlClient.Tests.Common.LocalAppContextSwitchesHelper; + namespace Microsoft.Data.SqlClient.ManualTesting.Tests { public static class DataReaderTest { private static readonly object s_rowVersionLock = new(); - // this enum must mirror the definition in LocalAppContextSwitches - private enum Tristate : byte - { - NotInitialized = 0, - False = 1, - True = 2 - } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] public static void LoadReaderIntoDataTableToTestGetSchemaTable() { @@ -269,33 +266,597 @@ public static void CheckNullRowVersionIsBDNull() { lock (s_rowVersionLock) { - Tristate originalValue = SetLegacyRowVersionNullBehavior(Tristate.False); + using SwitchesHelper helper = new(); + helper.LegacyRowVersionNullBehaviorField = SwitchesHelper.Tristate.False; + + using SqlConnection con = new(DataTestUtility.TCPConnectionString); + con.Open(); + using SqlCommand command = con.CreateCommand(); + command.CommandText = "select cast(null as rowversion) rv"; + using SqlDataReader reader = command.ExecuteReader(); + reader.Read(); + Assert.True(reader.IsDBNull(0)); + Assert.Equal(DBNull.Value, reader[0]); + var result = reader.GetValue(0); + Assert.IsType(result); + Assert.Equal(result, reader.GetFieldValue(0)); + Assert.Throws(() => reader.GetFieldValue(0)); + + SqlBinary binary = reader.GetSqlBinary(0); + Assert.True(binary.IsNull); + + SqlBytes bytes = reader.GetSqlBytes(0); + Assert.True(bytes.IsNull); + Assert.Null(bytes.Buffer); + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static int CanReadEmployeesTableCompletely() + { + int counter = 0; + + using (var conn = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + using (var cmd = new SqlCommand("SELECT EmployeeID,LastName,FirstName,Title,TitleOfCourtesy,BirthDate,HireDate,Address,City,Region,PostalCode,Country,HomePhone,Extension,Photo,Notes,ReportsTo,PhotoPath FROM Employees WHERE ReportsTo = @p0 OR (ReportsTo IS NULL AND @p0 IS NULL)", conn)) + { + cmd.Parameters.AddWithValue("@p0", 5); + + conn.Open(); + + using (var reader = cmd.ExecuteReader()) + { + if (reader.Read()) + { + for (int index = 0; index < reader.FieldCount; index++) + { + if (!reader.IsDBNull(index)) + { + object value = reader[index]; + counter += 1; + } + } + } + } + } + } + + return counter; + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static async Task CanReadEmployeesTableCompletelyAsync() + { + int counter = 0; + + using (var conn = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + using (var cmd = new SqlCommand("SELECT EmployeeID,LastName,FirstName,Title,TitleOfCourtesy,BirthDate,HireDate,Address,City,Region,PostalCode,Country,HomePhone,Extension,Photo,Notes,ReportsTo,PhotoPath FROM Employees WHERE ReportsTo = @p0 OR (ReportsTo IS NULL AND @p0 IS NULL)", conn)) + { + cmd.Parameters.AddWithValue("@p0", 5); + + await conn.OpenAsync(); + + using (var reader = await cmd.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + for (int index = 0; index < reader.FieldCount; index++) + { + if (!await reader.IsDBNullAsync(index)) + { + object value = reader[index]; + counter += 1; + } + } + } + } + } + } + + return counter; + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static async Task CanReadEmployeesTableCompletelyWithNullImageAsync() + { + int counter = 0; + + using (var conn = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + using (var cmd = new SqlCommand("SELECT EmployeeID,LastName,FirstName,Title,TitleOfCourtesy,BirthDate,HireDate,Address,City,Region,PostalCode,Country,HomePhone,Extension,convert(image,NULL) as Photo,Notes,ReportsTo,PhotoPath FROM Employees WHERE ReportsTo = @p0 OR (ReportsTo IS NULL AND @p0 IS NULL)", conn)) + { + cmd.Parameters.AddWithValue("@p0", 5); + + await conn.OpenAsync(); + + using (var reader = await cmd.ExecuteReaderAsync()) + { + if (await reader.ReadAsync()) + { + for (int index = 0; index < reader.FieldCount; index++) + { + if (!await reader.IsDBNullAsync(index)) + { + object value = reader[index]; + counter += 1; + } + } + } + } + } + } + + return counter; + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static async Task CanReadXmlData() + { + const string xml = @" + + Gambardella, Matthew + XML Developer's Guide + Computer + 44.95 + 2000-10-01 + An in-depth look at creating applications + with XML. + + + Ralls, Kim + Midnight Rain + Fantasy + 5.95 + 2000-12-16 + A former architect battles corporate zombies, + an evil sorceress, and her own childhood to become queen + of the world. + + + Corets, Eva + Maeve Ascendant + Fantasy + 5.95 + 2000-11-17 + After the collapse of a nanotechnology + society in England, the young survivors lay the + foundation for a new society. + + + Corets, Eva + Oberon's Legacy + Fantasy + 5.95 + 2001-03-10 + In post-apocalypse England, the mysterious + agent known only as Oberon helps to create a new life + for the inhabitants of London. Sequel to Maeve + Ascendant. + + + Corets, Eva + The Sundered Grail + Fantasy + 5.95 + 2001-09-10 + The two daughters of Maeve, half-sisters, + battle one another for control of England. Sequel to + Oberon's Legacy. + + + Randall, Cynthia + Lover Birds + Romance + 4.95 + 2000-09-02 + When Carla meets Paul at an ornithology + conference, tempers fly as feathers get ruffled. + + + Thurman, Paula + Splish Splash + Romance + 4.95 + 2000-11-02 + A deep sea diver finds true love twenty + thousand leagues beneath the sea. + + + Knorr, Stefan + Creepy Crawlies + Horror + 4.95 + 2000-12-06 + An anthology of horror stories about roaches, + centipedes, scorpions and other insects. + + + Kress, Peter + Paradox Lost + Science Fiction + 6.95 + 2000-11-02 + After an inadvertant trip through a Heisenberg + Uncertainty Device, James Salway discovers the problems + of being quantum. + + + O'Brien, Tim + Microsoft .NET: The Programming Bible + Computer + 36.95 + 2000-12-09 + Microsoft's .NET initiative is explored in + detail in this deep programmer's reference. + + + O'Brien, Tim + MSXML3: A Comprehensive Guide + Computer + 36.95 + 2000-12-01 + The Microsoft MSXML3 parser is covered in + detail, with attention to XML DOM interfaces, XSLT processing, + SAX and more. + + + Galos, Mike + Visual Studio 7: A Comprehensive Guide + Computer + 49.95 + 2001-04-16 + Microsoft Visual Studio 7 is explored in depth, + looking at how Visual Basic, Visual C++, C#, and ASP+ are + integrated into a comprehensive development + environment. + +"; + + string tableName = DataTestUtility.GenerateObjectName(); + + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString); + builder.PersistSecurityInfo = true; + builder.PacketSize = 3096; // should reproduce failure that this tests for in <100 reads + + using (var connection = new SqlConnection(builder.ToString())) + { + await connection.OpenAsync(); + try { - using SqlConnection con = new(DataTestUtility.TCPConnectionString); - con.Open(); - using SqlCommand command = con.CreateCommand(); - command.CommandText = "select cast(null as rowversion) rv"; - using SqlDataReader reader = command.ExecuteReader(); - reader.Read(); - Assert.True(reader.IsDBNull(0)); - Assert.Equal(DBNull.Value, reader[0]); - var result = reader.GetValue(0); - Assert.IsType(result); - Assert.Equal(result, reader.GetFieldValue(0)); - Assert.Throws(() => reader.GetFieldValue(0)); + // setup + using (var dropCommand = connection.CreateCommand()) + { + dropCommand.CommandText = $"DROP TABLE IF EXISTS [{tableName}]"; + dropCommand.ExecuteNonQuery(); + } + + using (var createCommand = connection.CreateCommand()) + { + createCommand.CommandText = $"CREATE TABLE [{tableName}] (Id int PRIMARY KEY, Data xml NOT NULL)"; + createCommand.ExecuteNonQuery(); + } + + for (var i = 0; i < 100; i++) + { + using (var insertCommand = connection.CreateCommand()) + { + insertCommand.CommandText = $"INSERT INTO [{tableName}] (Id, Data) VALUES (@id, @data)"; + insertCommand.Parameters.AddWithValue("@id", i); + insertCommand.Parameters.AddWithValue("@data", xml); + insertCommand.ExecuteNonQuery(); + } + } - SqlBinary binary = reader.GetSqlBinary(0); - Assert.True(binary.IsNull); + // execute + int id = 0; - SqlBytes bytes = reader.GetSqlBytes(0); - Assert.True(bytes.IsNull); - Assert.Null(bytes.Buffer); + using (var command = connection.CreateCommand()) + { + command.CommandText = $"SELECT Data FROM [{tableName}] ORDER BY Id"; + using (var reader = await command.ExecuteReaderAsync()) + { + string expectedResult = null; + while (await reader.ReadAsync()) + { + var result = reader.GetString(0); + if (expectedResult == null) + { + expectedResult = result; + } + else + { + Assert.Equal(expectedResult, result); + } + id++; + } + } + } + return id; } finally { - SetLegacyRowVersionNullBehavior(originalValue); + try + { + using (var dropCommand = connection.CreateCommand()) + { + dropCommand.CommandText = $"DROP TABLE IF EXISTS [{tableName}]"; + dropCommand.ExecuteNonQuery(); + } + } + catch + { + } + } + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static async Task CanReadSequentialDecreasingChunks() + { + // pattern repeat input allows you to more easily identify if chunks are incorrectly + // related to each other by seeing the start and end of sequential chunks and checking + // if they correctly move to the next char while debugging + // simply repeating a single char can't tell you where in the string it went wrong. + const string baseString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + StringBuilder inputBuilder = new StringBuilder(); + while (inputBuilder.Length < (64 * 1024)) + { + inputBuilder.Append(baseString); + inputBuilder.Append(' '); + } + + string input = inputBuilder.ToString(); + + StringBuilder resultBuilder = new StringBuilder(); + CancellationTokenSource cts = new CancellationTokenSource(); + using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + await connection.OpenAsync(cts.Token); + + using (var command = connection.CreateCommand()) + { + command.CommandText = "SELECT CONVERT(varchar(max),@str) as a"; + command.Parameters.AddWithValue("@str", input); + + using (var reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess, cts.Token)) + { + if (await reader.ReadAsync(cts.Token)) + { + using (var textReader = reader.GetTextReader(0)) + { + var buffer = new char[4096]; + var charsReadCount = -1; + var start = 0; + while (charsReadCount != 0) + { + charsReadCount = await textReader.ReadAsync(buffer, start, buffer.Length - start); + resultBuilder.Append(buffer, start, charsReadCount); + start++; + } + } + } + } + } + } + + string result = resultBuilder.ToString(); + + Assert.Equal(input, result); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static async Task CanReadBinaryData() + { + const int Size = 20_000; + + byte[] data = Enumerable.Range(0, Size) + .Select(i => (byte)(i % 256)) + .ToArray(); + string tableName = DataTestUtility.GenerateObjectName(); + + using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + await connection.OpenAsync(); + + try + { + using (var createCommand = connection.CreateCommand()) + { + createCommand.CommandText = $@" +DROP TABLE IF EXISTS [{tableName}] +CREATE TABLE [{tableName}] (Id INT IDENTITY(1,1) PRIMARY KEY, Data VARBINARY(MAX)); +INSERT INTO [{tableName}] (Data) VALUES (@data);"; + createCommand.Parameters.Add(new SqlParameter("@data", SqlDbType.VarBinary, Size) { Value = data }); + await createCommand.ExecuteNonQueryAsync(); + } + + using (var command = connection.CreateCommand()) + { + + command.CommandText = $"SELECT Data FROM [{tableName}]"; + command.Parameters.Clear(); + var result = (byte[])await command.ExecuteScalarAsync(); + + Assert.Equal(data, result); + } + + } + finally + { + try + { + using (var dropCommand = connection.CreateCommand()) + { + dropCommand.CommandText = $"DROP TABLE IF EXISTS [{tableName}]"; + dropCommand.ExecuteNonQuery(); + } + } + catch + { + } + } + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static async Task CanGetCharsSequentially() + { + const CommandBehavior commandBehavior = CommandBehavior.SequentialAccess | CommandBehavior.SingleResult; + const int length = 32000; + const string sqlCharWithArg = "SELECT CONVERT(BIGINT, 1) AS [Id], CONVERT(NVARCHAR(MAX), @input) AS [Value];"; + + using (var sqlConnection = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + await sqlConnection.OpenAsync(); + + StringBuilder inputBuilder = new StringBuilder(length); + Random random = new Random(); + for (int i = 0; i < length; i++) + { + inputBuilder.Append((char)random.Next(0x30, 0x5A)); + } + string input = inputBuilder.ToString(); + + using (var sqlCommand = new SqlCommand()) + { + sqlCommand.Connection = sqlConnection; + sqlCommand.CommandTimeout = 0; + sqlCommand.CommandText = sqlCharWithArg; + sqlCommand.Parameters.Add(new SqlParameter("@input", SqlDbType.NVarChar, -1) { Value = input }); + + using (var sqlReader = await sqlCommand.ExecuteReaderAsync(commandBehavior)) + { + if (await sqlReader.ReadAsync()) + { + long id = sqlReader.GetInt64(0); + if (id != 1) + { + Assert.Fail("Id not 1"); + } + + var sliced = GetPooledChars(sqlReader, 1, input); + if (!sliced.SequenceEqual(input.ToCharArray())) + { + Assert.Fail("sliced != input"); + } + } + } + } + } + + static char[] GetPooledChars(SqlDataReader sqlDataReader, int ordinal, string input) + { + var buffer = ArrayPool.Shared.Rent(8192); + int offset = 0; + while (true) + { + int read = (int)sqlDataReader.GetChars(ordinal, offset, buffer, offset, buffer.Length - offset); + if (read == 0) + { + break; + } + + ReadOnlySpan fetched = buffer.AsSpan(offset, read); + ReadOnlySpan origin = input.AsSpan(offset, read); + + if (!fetched.Equals(origin, StringComparison.Ordinal)) + { + Assert.Fail($"chunk (start:{offset}, for:{read}), is not the same as the input"); + } + + offset += read; + + if (buffer.Length - offset < 128) + { + buffer = Resize(buffer); + } + } + + var sliced = buffer.AsSpan(0, offset).ToArray(); + ArrayPool.Shared.Return(buffer); + return sliced; + + static char[] Resize(char[] buffer) + { + var newBuffer = ArrayPool.Shared.Rent(buffer.Length * 2); + Array.Copy(buffer, newBuffer, buffer.Length); + ArrayPool.Shared.Return(buffer); + return newBuffer; + } + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static async Task CanReadLargeNTextColumn() + { + string stringValue = "CgRtOjY2CgVtOjIxMwoGbTo0ODYwCgZtOjQ4MzYKBG06MjAKBW06MTUxCgVtOjE1MgoFbToxNTMKBW06MTU0CgVtOjE0NwoFbToxNDgKBW06MTQ5CgVtOjE1MAoFbTozMzUKBm06NDg3MwoFbTo0NTQKBW06MTczCgVtOjQzMgoGbTo1OTAwCgVtOjQzMQoGbTo1ODk5CgVtOjQzNAoFbTo0MzMKBW06NDI4CgZtOjU4OTYKBW06NDI3CgZtOjU4OTUKBm06NTExOQoLbTo5OTk4MDYwMDMKBW06MTk5CgVtOjMxMgoGbTo0NjEyCgZtOjUxMDAKBW06MzIwCgZtOjQ2MjAKBm06NDg0MQoFbTozMTAKBm06NDYxMAoEbTo2NwoFbTozMjQKBm06NDYyNAoGbTo0ODYzCgVtOjMxNQoGbTo0NjE1CgZtOjQ4NjEKBm06NDg2MgoKbTo5OTk4MDIyNAoKbTo5OTk4MDIyMwoKbTo5OTk4MDIyMAoKbTo5OTk4MDIyMgoKbTo5OTk4MDIyMQoGbTo0ODM1CgptOjk5OTgwMjI2CgptOjk5OTgwMjI3CgZtOjkxMDIKBm06OTEwMwoGbTo5MTE3CgZtOjkxMTgKBW06MTE5CgVtOjEyNQoEbTozOAoEbTo0NgoEbTo1NAoFbToxNjAKBW06MTE0CgRtOjQwCgRtOjQ4CgRtOjU2CgVtOjE2MgoFbToxMTYKBG06MzYKBG06NDQKBG06NTIKBW06MTU4CgVtOjExMgoEbTozNAoEbTo0MgoEbTo1MAoFbToyMTQKBW06MTU2CgVtOjExMAoLbTo5OTk4MDQwMDYKC206OTk5ODA0MDA3CgVtOjEwMgoGbTo5MTMyCgZtOjkxMzMKBW06Mjc3CgZtOjQxMjQKA206MwoDbTo0CgNtOjUKBG06MTkKBG06MjUKBG06MjYKBG06MjcKBW06Mjg1CgVtOjI4NwoFbToyODkKBm06NDM2MAoGbTo0MzYyCgZtOjQzNjQKBW06MjE3CgRtOjg1CgZtOjUzNzIKBm06NTM3MQoGbTo2NjU3CgZtOjY2NjUKBm06NjY1OAoGbTo2NjY2CgZtOjY2NTkKBm06NjY2NwoGbTo2NjYwCgZtOjY2NjgKBW06MjAyCgVtOjE5NgoFbToxOTgKBW06MTk3CgVtOjMxMQoGbTo0NjExCgVtOjMyMQoGbTo0NjIxCgVtOjMxNgoGbTo0NjE2CgVtOjMxNwoGbTo0NjE3CgVtOjMxOQoGbTo0NjE5CgVtOjMyMgoGbTo0NjIyCgVtOjMyMwoGbTo0NjIzCgVtOjMxOAoGbTo0NjE4CgVtOjMyNgoFbTozMDkKBm06NDYwOQoEbTo2OAoGbTo0ODU5CgZtOjQ4NTMKBW06MzE0CgZtOjQ2MTQKBm06NDg1MAoGbTo0ODUxCgZtOjQ4NTIKBm06NDg1OAoGbTo0ODQ3CgZtOjQ4NDgKBm06NDg0OQoGbTo0ODQ0CgZtOjQ4NDMKBm06NDg0MgoGbTo0ODQ1CgZtOjQ4NDYKBm06NDg1NQoGbTo0ODU2CgZtOjQ4NTQKBm06NDg1NwoKbTo5OTk4MDIxOQoKbTo5OTk4MDIyNQoKbTo5OTk4MDIyOAoGbTo2NjYxCgZtOjY2NjkKBm06NjY2MgoGbTo2NjcwCgZtOjY2NjMKBm06NjY3MQoGbTo2NjY0CgZtOjY2NzIKBW06MTMxCgRtOjk3CgRtOjM3CgRtOjQ1CgRtOjUzCgVtOjE1OQoFbToxMTMKBW06MTQ0CgRtOjM5CgRtOjQ3CgRtOjU1CgVtOjE2MQoFbToxMTUKBG06MzUKBG06NDMKBG06NTEKBW06MTU3CgVtOjExMQoEbTozMwoEbTo0MQoEbTo0OQoFbToxNTUKBW06MTA5CgVtOjEzMgoFbToxMzAKBm06NjE0MQoLbTo5OTk4MDcwMTkKBW06NDI0CgZtOjU4OTIKBm06OTE3OAoGbTo5MTc5CgZtOjkxODAKBm06OTE4MQoGbTo5MTgyCgZtOjkxODMKBm06OTE4NAoGbTo5MTg1CgZtOjkxODYKBm06OTE4NwoGbTo5MTg4CgZtOjkxODkKBm06OTE5MAoGbTo5MTkxCgZtOjkxOTIKBm06OTE5MwoFbTo0MzAKBm06NTg5OAoFbTo0MjkKBm06NTg5NwoGbTo5MTM3CgVtOjQ0NAoFbTo0NDUKBm06NTEyMAoGbTo2NDAwCgZtOjUzNzYKBm06NTYzMgoGbTo0MDk2CgZtOjYxNDQKBm06NDg2NAoGbTo1ODg4CgZtOjY2NTYKBm06NDM1MgoGbTo0NjA4CgVtOjEwNgoEbToxMQoGbTo1Mzc0CgZtOjUzNzUKBW06MjkyCgZtOjQzNjcKBm06OTA4NQoGbTo5MDg2CgZtOjUxMTUKBm06NTExNgoGbTo1MTEzCgZtOjUxMTQKBm06NTExNwoGbTo1MTEyCgZtOjUxMTEKBm06OTE0MwoGbTo5MTQ0CgZtOjYxMzgKBW06MTI5CgVtOjQxMQoFbTo0MTIKBm06NDg2NQoGbTo0ODY2CgVtOjMyNwoFbTozMjgKC206OTk5ODAzMDkzCgZtOjUxMDIKBW06MzQxCgZtOjQ4NzkKBm06NTEwOAoFbTozMzQKBm06NDg3MgoGbTo1MDkyCgZtOjUwOTEKBW06MzQ5CgZtOjQ4ODcKBW06MzQ4CgZtOjQ4ODYKBW06MzQ2CgZtOjQ4ODQKBW06MzQ3CgZtOjQ4ODUKBW06NDA3CgVtOjM1NgoGbTo0ODk0CgZtOjUxMDEKBW06MzgzCgZtOjQ5MjIKBW06Mzk1CgZtOjQ5MTkKBG06MTYKBm06NTA5NQoGbTo1MDk0CgZtOjUwOTMKBW06MzQzCgZtOjQ4ODEKBW06MzQyCgZtOjQ4ODAKBG06ODMKBW06MzQ1CgZtOjQ4ODMKBG06ODQKBm06NDkyMwoFbTozODQKC206OTk5ODAzMDg4CgVtOjEyMAoFbTo0MDUKC206OTk5ODAzMDg5CgVtOjMzMgoGbTo0ODcwCgVtOjM4NwoGbTo0OTI2CgZtOjUxMDMKBW06MzgxCgVtOjM3OQoGbTo0OTIwCgZtOjQ5MTcKBm06NTA5NwoLbTo5OTk4MDMwOTkKC206OTk5ODAzMTAwCgttOjk5OTgwMzEwMQoLbTo5OTk4MDMxMDIKC206OTk5ODAzMTAzCgRtOjk5CgRtOjMxCgVtOjM0NAoGbTo0ODgyCgVtOjE4MQoFbTozMzAKBm06NDg2OAoFbToxODIKBG06NzkKBG06ODEKBW06MzQwCgZtOjQ4NzgKBW06MzM5CgZtOjQ4NzcKBW06MzU3CgZtOjQ4OTUKBW06MzU4CgZtOjQ4OTYKBm06NTEwNwoFbTozNTQKBm06NDg5MgoFbTozNTEKBm06NDg4OQoFbTozNTAKBm06NDg4OAoFbTozNTMKBm06NDg5MQoFbTozNTIKBm06NDg5MAoLbTo5OTk4MDMxMzUKC206OTk5ODAzMTM2CgttOjk5OTgwMzE0MQoLbTo5OTk4MDMxNDIKC206OTk5ODAzMTQwCgttOjk5OTgwMzEzOAoLbTo5OTk4MDMxMzkKC206OTk5ODAzMTM3CgttOjk5OTgwMzA5MQoLbTo5OTk4MDMwOTIKC206OTk5ODAzMDkwCgVtOjQwOQoFbTo0MTAKBW06NDA4CgttOjk5OTgwMzE0NgoLbTo5OTk4MDMxNDcKBG06OTgKBm06NDg3NAoFbTozMzYKBm06NTA5NgoFbTozMzgKBW06MzM3CgZtOjQ4NzYKBm06NDg3NQoFbTozNzgKBm06NDkxNgoFbTozOTAKBm06NDkyOQoFbTozOTIKBm06NDkzMQoFbTozOTMKBm06NDkzMgoFbTo0MDQKBW06MzU5CgZtOjQ4OTcKBm06NDg5OAoFbTozNjAKBW06NDAxCgVtOjQwMgoFbTo0MDMKBW06Mzc2CgZtOjQ5MTQKC206OTk5ODAzMTA4CgttOjk5OTgwMzEwOQoLbTo5OTk4MDMxMTAKC206OTk5ODAzMTExCgZtOjQ5MjcKBW06Mzg4CgVtOjMzMQoGbTo0ODY5CgVtOjM1NQoGbTo0ODkzCgVtOjMzMwoGbTo0ODcxCgVtOjQwMAoGbTo0OTM4CgVtOjM3NQoGbTo0OTEzCgZtOjQ5MzUKBW06Mzk3CgttOjk5OTgwMzExMwoLbTo5OTk4MDMxMTQKC206OTk5ODAzMTE1CgttOjk5OTgwMzExNgoEbTozMAoGbTo1MTA2CgZtOjUxMDUKC206OTk5ODAzMTEyCgZtOjQ5MjUKBm06NDkyNAoLbTo5OTk4MDMxNDkKC206OTk5ODAzMTQ4CgVtOjM4NgoFbTozODUKBW06MTIzCgttOjk5OTgwMzA4NwoLbTo5OTk4MDMxMDQKC206OTk5ODAzMTA1CgttOjk5OTgwMzEwNgoLbTo5OTk4MDMxMDcKBW06Mzk4CgZtOjQ5MzYKBW06Mzk5CgZtOjQ5MzcKC206OTk5ODAzMTQ1CgVtOjEwMAoFbTozODIKBm06NDkyMQoFbTozODAKBm06NDkxOAoFbToxMDgKBW06NDIzCgZtOjU4OTEKBW06NDIyCgZtOjU4OTAKBW06MTIyCgVtOjQyMQoGbTo5MTQ1CgZtOjU4ODkKBm06NTM2OQoEbToxOAoGbTo5MTQxCgZtOjkxNDIKBW06MTAzCgRtOjE1CgVtOjE3NAoFbToxNzUKBW06MTc2CgVtOjQ1MQoFbTo0NTIKBW06NDUzCgVtOjQ0OAoFbTo0NDkKBW06NDUwCgRtOjEzCgZtOjkxMjIKBm06OTEyMwoFbTozOTQKBm06NDkzMwoGbTo5MTMwCgZtOjkxOTYKBm06OTEyOQoGbTo5MTk1CgZtOjkxMjgKBm06OTE5NAoGbTo5MTMxCgZtOjkxOTcKBG06MjEKBW06MTgwCgVtOjE3MAoFbToyMDMKBW06MTI4CgVtOjEwMQoFbTo0MTMKBm06NTEyMQoFbTo0MTQKBm06NTEyMgoGbTo0MzUxCgZtOjkxMjYKBm06OTEyNQoGbTo5MTI0CgttOjk5OTgwMzA5NAoLbTo5OTk4MDMwOTUKC206OTk5ODAzMDk2CgttOjk5OTgwMzA5NwoLbTo5OTk4MDMwOTgKBW06MTI3CgptOjk5OTgwMTM3CgptOjk5OTgwMTM4CgVtOjEyNgoFbTozMDgKBm06OTEwMQoKbTo5OTk4MDEzMgoGbTo0MzgzCgVtOjE4NQoFbToxODMKBW06MTg0CgVtOjE4OAoFbToxODYKBW06MTg3CgVtOjE5MQoFbToxODkKBW06MTkwCgVtOjE5NAoFbToxOTIKBW06MTkzCgRtOjg5CgRtOjg2CgRtOjkwCgRtOjg3CgRtOjkxCgRtOjg4CgVtOjQyMAoGbTo1NjM0CgVtOjEwNwoEbToxNAoFbToxMzQKBW06MTMzCgRtOjI4CgRtOjEyCgVtOjI5MAoFbToyOTEKBm06NTM2OAoEbTo2NQoFbToxMDQKBG06NzAKBm06OTA2NwoGbTo5MDY4CgVtOjI3NAoGbTo0MTIyCgVtOjI3NQoGbTo0MTIxCgVtOjE0MQoEbTo5NgoFbToyNTgKBm06NDEwNQoFbToyNTkKBm06NDEwNgoLbTo5OTk4MDMxNTAKC206OTk5ODAzMTQzCgVtOjM5MQoGbTo0OTMwCgVtOjE3OQoEbTo3MgoGbTo2MTQzCgZtOjYxNDIKCm06OTk5ODAxMzMKCm06OTk5ODAxMzQKBW06MTY5CgVtOjIwNAoFbToyMDYKBW06MjA4CgVtOjIxNgoFbToyODQKBm06OTA3OAoGbTo5MDc3CgVtOjI4NgoGbTo5MDgwCgZtOjkwNzkKBW06Mjg4CgZtOjkwODIKBm06OTA4MQoGbTo0MzU5CgZtOjQzNjEKBm06NDM2MwoGbTo0NjAzCgZtOjQ2MDIKBm06NDYwNQoGbTo0NjA0CgZtOjQ2MDcKBm06NDYwNgoFbToyOTMKBm06NDM2OAoFbToyMTEKBW06MjEyCgVtOjIwNQoFbToyMDcKBW06MjA5CgZtOjQ1ODcKBW06MjU3CgZtOjQxMDQKBG06MjkKBW06Mjk1CgZtOjQzNzAKBm06NDM0OAoGbTo0MzQ5CgZtOjQzNDcKBm06NDM0MwoGbTo0MzQ1CgZtOjQzNDYKBm06NDM0NAoGbTo1MDk5CgZtOjQ4NDAKBm06OTExNAoGbTo5MTA5CgZtOjkxMjAKBm06OTExMAoGbTo5MTEyCgZtOjkxMTUKBm06OTExNgoGbTo5MTExCgZtOjkxMTkKBm06NDgzOAoGbTo0ODM3CgVtOjE0MAoFbToxMzgKBW06MTQ2CgVtOjE0MwoFbToxMzkKBm06NDgzOQoFbToxNDIKBW06MTQ1CgVtOjMyNQoGbTo0NjI1CgZtOjkxMDQKBm06OTEwNQoGbTo5MTA3CgZtOjkxMDgKBW06MTA1CgRtOjEwCgRtOjk1CgNtOjAKA206MQoDbToyCgRtOjkyCgRtOjkzCgRtOjk0CgttOjk5OTgwMzEyMgoLbTo5OTk4MDMxMTcKC206OTk5ODAzMTIzCgttOjk5OTgwMzEyNAoLbTo5OTk4MDMxMjEKC206OTk5ODAzMTE4CgttOjk5OTgwMzExOQoLbTo5OTk4MDMxMzQKC206OTk5ODAzMTI4CgttOjk5OTgwMzEyOQoLbTo5OTk4MDMxMzAKC206OTk5ODAzMTI2CgttOjk5OTgwMzEyNwoLbTo5OTk4MDMxMjUKC206OTk5ODAzMTMxCgttOjk5OTgwMzEzMgoLbTo5OTk4MDMxMzMKBm06NDkwMwoFbTozNjUKBW06MzY2CgZtOjQ5MDQKBm06NDkwOQoFbTozNzEKBW06MzY5CgZtOjQ5MDcKBW06NDA2CgVtOjM2MwoGbTo0OTAxCgVtOjM2OAoGbTo0OTA2CgVtOjM2NwoGbTo0OTA1CgVtOjM3MAoGbTo0OTA4CgVtOjM2NAoGbTo0OTAyCgVtOjM2MQoGbTo0ODk5CgVtOjM3MgoGbTo0OTEwCgVtOjM3MwoGbTo0OTExCgVtOjM3NAoGbTo0OTEyCgVtOjEyNAoFbTozNjIKBm06NDkwMAoLbTo5OTk4MDMxNDQKBW06Mzk2CgZtOjQ5MzQKBG06NzUKC206OTk5ODA2MDA0CgZtOjYxNDAKBm06NjEzOQoGbTo2MTIwCgVtOjExOAoEbTo3OAoGbTo5MTIxCgZtOjYxMjEKBm06NjEyOAoGbTo2MTMwCgZtOjYxMzEKBm06NjEzMgoGbTo2MTI5CgZtOjYxMjYKBm06NjEyNAoGbTo2MTI1CgZtOjkxNTkKBm06NjEyMwoGbTo2MTIyCgZtOjYxMzMKBW06MTY2CgVtOjE2NwoFbToxNjgKBG06MTcKBm06NTEwOQoGbTo1MTE4CgZtOjUxMTAKBW06MTcxCgVtOjE3MgoGbTo2NDA0CgZtOjkwNDkKBm06OTE2NAoGbTo5MDQ3CgZtOjkxNjIKBm06OTA0OAoGbTo5MTYzCgZtOjkwNTUKBm06OTA1NgoGbTo5MDU3CgZtOjkwNTgKBm06OTA1OQoGbTo5MDYwCgZtOjkxNzQKBm06OTE3MgoGbTo5MTczCgZtOjkxMTMKCm06OTk5ODAyMjkKBW06MzEzCgZtOjQ2MTMKBm06OTEwNgoEbTo3MQoFbTo0MTkKBm06NTYzMwoFbToxMjEKBW06MjE5CgVtOjIxOAoFbToyMjEKBW06MjIwCgVtOjIyMwoFbToyMjIKBm06NDU5OQoGbTo0NTk4CgVtOjI2MAoGbTo0MTA3CgZtOjkwNTMKBW06MjYxCgZtOjQxMDgKBm06OTA1NAoGbTo0NTkzCgZtOjQ1OTIKBm06NDU5MQoGbTo0NTkwCgZtOjQ1ODkKBm06NDU4OAoGbTo0NTk1CgZtOjQ1OTQKBm06NDU5NwoGbTo0NTk2CgZtOjQzNTAKBW06MjU2CgZtOjQxMDMKBm06NDYwMQoGbTo0NjAwCgZtOjkwODcKBm06OTA4OAoFbToyOTQKBm06NDM2OQoGbTo2MTQ1CgZtOjYxNDYKBW06Mjc2CgZtOjQxMjMKBG06NzQKBG06NzMKBm06OTA1MgoGbTo5MTY3CgZtOjkwNTAKBm06OTE2NQoGbTo5MDUxCgZtOjkxNjYKBW06MTYzCgVtOjE2NAoFbToxNjUKBm06OTA2MQoGbTo5MDYyCgZtOjkwNjMKBm06OTA2NAoGbTo5MDY1CgZtOjkwNjYKBm06OTA2OQoGbTo5MDcwCgZtOjkwNDEKBm06OTA0MgoGbTo5MDQzCgZtOjkwNDQKBm06OTA0NQoGbTo5MDQ2CgZtOjYxMTkKBG06MjIKBG06MjMKBG06MjQKBm06NDM2NQoGbTo5MDgzCgZtOjQzNjYKBm06OTA4NAoKbTo5OTk4MDEzNQoFbTo0MTcKBm06NTM3NwoFbToyMDAKBG06NjkKBm06OTE2MAoGbTo5MTYxCgZtOjQ1ODYKBm06NDU4NQoGbTo2NDAyCgZtOjY0MDMKC206OTk5ODAzMTIwCgZtOjYxMzcKBW06MjAxCgVtOjIxNQoGbTo5MTU2CgZtOjkxNTUKBm06OTE1MgoGbTo5MTUxCgZtOjkxNTgKBm06OTE0OAoGbTo5MTU0CgZtOjkxNTMKBm06OTE0NwoGbTo5MTQ2CgZtOjkxNTcKBW06MTc4CgVtOjE3NwoGbTo2MTM0CgZtOjkxNTAKBm06OTE0OQoGbTo2MTM1CgZtOjYxMzYKBW06MjEwCgRtOjc2CgRtOjc3CgRtOjgyCgZtOjUwOTgKBm06NjEyNwoEbTozMgoGbTo1MzcwCgZtOjkxMzgKBm06OTE0MAoGbTo5MTM5CgZtOjUzNzMKBG06ODAKBW06NDM4CgVtOjQzNwoFbTo0MzYKBW06NDM1CgZtOjY0MDEKBm06NjQwNgoGbTo2NDA1CgZtOjkxNzcKBm06OTE3NQoGbTo5MTc2CgZtOjEwMDAKBm06MTAwMQoEbTo2NAoKbTo5OTk4MDEzNgoFbToxOTUKBW06MTE3CgttOjk5OTgwNDAwNQoFbTo0MTYKBm06NTEyNAoFbTo0MTUKBm06NTEyMwoGbTo5MTcwCgZtOjkxNjgKBm06OTE2OQoGbTo5MTcxCgZtOjY0MDcKBm06NjQwOAoFbTozMjkKBm06NDg2NwoGbTo0OTI4CgVtOjM4OQoGbTo5MTI3CgZtOjUxMDQKBW06NDE4CgZtOjUzNzgKBW06MjUxCgZtOjkwODkKBm06OTA5MAoFbToyOTYKBW06Mjk3CgVtOjI2MgoFbToyNjMKBm06OTA3MQoGbTo5MDcyCgZtOjkwOTUKBm06OTA5NgoFbTozMDIKBW06MzAzCgVtOjI2OAoFbToyNjkKBW06MjUwCgVtOjI3OAoFbToyNzkKBW06MjUzCgZtOjkwOTEKBm06OTA5MgoFbToyOTgKBW06Mjk5CgVtOjI2NAoFbToyNjUKBm06OTA3MwoGbTo5MDc0CgZtOjkwOTcKBm06OTA5OAoFbTozMDQKBW06MzA1CgVtOjI3MAoFbToyNzEKBW06MjUyCgVtOjI4MAoFbToyODEKBW06MjU1CgZtOjkwOTMKBm06OTA5NAoFbTozMDAKBW06MzAxCgVtOjI2NgoFbToyNjcKBm06OTA3NQoGbTo5MDc2CgZtOjkwOTkKBm06OTEwMAoFbTozMDYKBW06MzA3CgVtOjI3MgoFbToyNzMKBW06MjU0CgVtOjI4MgoFbToyODMKBm06NDA5OAoGbTo0MzcxCgZtOjQzNzIKBm06NDEwOQoGbTo0MTEwCgZtOjQzNzcKBm06NDM3OAoGbTo0MTE1CgZtOjQxMTYKBm06NDA5NwoGbTo0MzUzCgZtOjQzNTQKBm06NDEwMAoGbTo0MzczCgZtOjQzNzQKBm06NDExMQoGbTo0MTEyCgZtOjQzNzkKBm06NDM4MAoGbTo0MTE3CgZtOjQxMTgKBm06NDA5OQoGbTo0MzU1CgZtOjQzNTYKBm06NDEwMgoGbTo0Mzc1CgZtOjQzNzYKBm06NDExMwoGbTo0MTE0CgZtOjQzODEKBm06NDM4MgoGbTo0MTE5CgZtOjQxMjAKBm06NDEwMQoGbTo0MzU3CgZtOjQzNTgKBW06Mzc3CgZtOjQ5MTUKBm06OTE5OQoGbTo5MTk4CgVtOjQyNgoGbTo1ODk0CgVtOjQyNQoGbTo1ODkzCgVtOjEzNwoFbToxMzUKBW06MTM2CgZtOjkxMzUKBm06OTEzNAoGbTo5MTM2CgNkOjcKA2Q6NAoDZDoxCgNkOjgKA2Q6OQoDZDoyCgNkOjUKA2Q6MwoDZDo2CgRkcDoyCgVkcDozNgoFZHA6MjkKBWRwOjE3CgRkcDo4CgVkcDozMAoEZHA6NwoEZHA6MwoFZHA6MjgKBWRwOjMyCgVkcDoxMAoFZHA6MjcKBWRwOjMzCgVkcDoyMQoFZHA6MTMKBWRwOjI2CgVkcDozOAoFZHA6MTgKBWRwOjI0CgRkcDo5CgVkcDozMQoFZHA6MTYKBWRwOjExCgVkcDoxOQoFZHA6MjIKBWRwOjIzCgVkcDoyMAoFZHA6MzQKBWRwOjM1CgRkcDo2CgVkcDozNwoEZHA6NQoFZHA6MTUKBWRwOjI1CgRkcDo0CgRkcDoxCgVkcDoxMgoFZHA6MTQKBGk6MjYKBWk6MTY0CgVpOjIyMQoFaToyMjIKBGk6MTQKBWk6MTgxCgNpOjQKBGk6MTAKBGk6MTYKBWk6MTUxCgRpOjE3CgVpOjE0OQoEaTo5NAoEaTo5MwoFaToyMjUKBWk6MjE4CgVpOjIxNgoFaToyNjQKBWk6MjY1CgVpOjI1OQoFaToxODUKBGk6OTEKBGk6OTIKBGk6MjEKBWk6Mjc4CgRpOjcwCgRpOjcxCgRpOjU2CgRpOjU1CgVpOjI4MgoFaToyMDIKBWk6MjA2CgRpOjc0CgRpOjUwCgRpOjgzCgRpOjgyCgRpOjgxCgVpOjI4OQoEaTozNAoFaToyNzIKBWk6MjE3CgVpOjI0NwoFaToyNDYKBWk6MTYxCgRpOjg4CgVpOjIwNwoFaToyMDgKBGk6NDMKBGk6NjYKBWk6MjUzCgVpOjI1NAoEaToxMQoEaToxMgoEaTo5MAoEaTo1MwoEaTozOAoFaToyODEKBWk6MTc5CgVpOjE4OQoFaToyMjYKBWk6MjAxCgRpOjI0CgRpOjI1CgVpOjE1MwoDaToxCgRpOjk2CgRpOjk1CgVpOjEzMQoFaToxMzMKBWk6MTMyCgVpOjE5NQoFaToyNTgKBGk6MzAKBGk6NzcKBGk6MzUKBGk6MjcKBGk6NDkKBGk6MzkKBWk6MjYzCgRpOjQ1CgRpOjQ2CgRpOjQ0CgVpOjI4NQoFaToyODQKBWk6MTQyCgVpOjE0NQoFaToxNTIKBWk6MTY2CgVpOjE2NQoFaToxNTgKBWk6MTYzCgVpOjE0NAoFaToxNDMKBWk6MjczCgVpOjI3NAoFaToyNzUKBWk6MjQyCgVpOjEwMwoFaToxNzIKBWk6MTcxCgVpOjI3MQoFaToxMDAKBWk6MTAxCgRpOjk4CgRpOjk5CgRpOjg3CgVpOjEyOAoFaToxMTAKBWk6MTA5CgVpOjEwNgoFaToxMjMKBWk6MTI3CgVpOjExMgoFaToxMDgKBWk6MTExCgVpOjExNgoFaToxMTUKBWk6MTI5CgVpOjEwNwoFaToxMTkKBWk6MTEzCgVpOjExNAoFaToxMTcKBWk6MTIwCgVpOjExOAoFaToxMjEKBWk6MTIyCgVpOjEyNgoFaToxMjQKBWk6MTI1CgRpOjc1CgVpOjI2OAoFaToyNjkKBWk6MTU5CgVpOjI2NgoFaToyNzAKBWk6MTY4CgRpOjEzCgVpOjE1MAoFaToxNjcKBWk6MjU3CgVpOjIxOQoEaTo2MAoEaTo1OQoFaToxMzcKBWk6MTM4CgVpOjIyNwoDaTo5CgVpOjI0MwoEaTo2NQoFaToyMjMKBWk6MjI0CgRpOjY4CgRpOjY0CgVpOjE2MAoFaToyMDMKBGk6ODAKBWk6MTk3CgVpOjE4MgoEaToyOAoEaTo3OQoEaTo4NQoFaToyNjcKBGk6NTEKBGk6NzMKBGk6NzgKBGk6ODQKBGk6ODkKBWk6MTM0CgVpOjE3MwoFaToxMDQKA2k6NwoEaToyMgoEaTo1MgoFaToxNDYKBWk6MTk0CgRpOjM2CgRpOjQwCgVpOjE2MgoFaToyNDUKBWk6MTk5CgRpOjY5CgRpOjY3CgVpOjE1NAoFaToxNzYKBWk6MjIwCgVpOjIzMwoFaToxNTYKBWk6MTkzCgRpOjg2CgVpOjE5OAoFaToyNzcKBWk6MjA5CgVpOjIxMAoFaToyMzIKBWk6MjE1CgRpOjYyCgRpOjMyCgVpOjE3MAoFaToxNTcKBWk6MjUyCgRpOjU0CgRpOjQxCgVpOjE1NQoFaToyNzkKBWk6MTY5CgVpOjE0NwoFaToxNDgKBGk6MzcKBGk6MjkKBWk6MTc4CgVpOjE3NwoFaToyMTMKBGk6MzMKBWk6MjgzCgVpOjE3NQoEaTo1OAoEaToyMAoEaTo2MwoEaToxNQoDaTozCgVpOjIwMAoDaToyCgVpOjI2MQoFaToyODYKBWk6MTM1CgVpOjEzNgoFaToyNzYKBGk6NDcKA2k6OAoEaTo5NwoFaToyODgKBWk6MTM5CgVpOjI4NwoFaToxOTAKBWk6MTkyCgVpOjE5MQoEaToxOAoEaTo3NgoFaToxMzAKBWk6MjA0CgVpOjI2MAoFaToyMzEKBWk6MjE0CgRpOjYxCgVpOjE0MQoFaToyODAKBWk6MjI4CgVpOjIyOQoFaToyMzQKBWk6MjM1CgVpOjI1MAoFaToyNTEKBWk6MTg2CgVpOjE4OAoFaToxODcKBWk6MjMwCgRpOjQyCgVpOjE5NgoFaToyNDAKBWk6MjM5CgVpOjE0MAoEaToyMwoFaToyNDEKBWk6MTc0CgRpOjcyCgVpOjIwNQoFaToyNTYKBWk6MjU1CgRpOjE5CgRpOjQ4CgNpOjYKBWk6MTAyCgVpOjIxMQoFaToyMTIKBGk6NTcKBWk6MjYyCgVpOjI0NAoFaToyMzYKBWk6MjM3CgVpOjIzOAoDaTo1CgVpOjEwNQoFaToyNDgKBWk6MjQ5CgVpOjE4MwoFaToxODQKBGk6MzEKBWk6MTgwCgN0OjMKBHQ6MTEKBHQ6MTAKBHQ6MjEKA3Q6NAoDdDo2CgN0OjkKA3Q6NwoDdDo4CgR0OjEzCgR0OjE4CgR0OjEyCgR0OjE5CgR0OjE3CgN0OjUKBHQ6MTUKA3Q6MQoEdDoyMAoDdDoyCgR0OjE2CgR0OjE0"; + + using (var cn = new Microsoft.Data.SqlClient.SqlConnection(DataTestUtility.TCPConnectionString)) + { + await cn.OpenAsync(); + + string tableName = DataTestUtility.GenerateObjectName(); + + try + { + using (var cmd = cn.CreateCommand()) + { + cmd.CommandText = $""" + create table [{tableName}] + ( + User_ID varchar(22) not null, + StringName varchar(255) not null, + IsGlobal bit not null, + List ntext, + UseProtoSerializer bit, + ModuleNameForUse tinyint, + IsReadOnly bit not null, + VersionNumber smallint not null, + UserGlobalSet_ID uniqueidentifier not null, + IsProtoCorrected bit default 1 + ) + + insert into [{tableName}](User_ID, StringName, IsGlobal, List, UseProtoSerializer, ModuleNameForUse, + IsReadOnly, VersionNumber, UserGlobalSet_ID, IsProtoCorrected) + values ('80004Q4WZ1350KO8NT59RM', '_', 1, '{stringValue}', 1, 2, 1, 1, newid(), 1); + + """; + + await cmd.ExecuteNonQueryAsync(); + + cmd.CommandText = $"""" + SELECT + [gs].[List], + [gs].[UseProtoSerializer] + FROM + [{tableName}] [gs] + WHERE + ([gs].[IsGlobal] = 1 OR [gs].[User_ID] = '{"80004Q4WZ1350KO8NT59RM"}') AND + ([gs].[ModuleNameForUse] IS NULL OR [gs].[ModuleNameForUse] = {2}) + + """"; + + var l = (string)await cmd.ExecuteScalarAsync(); + Assert.Equal(stringValue, l); + + } + } + finally + { + try + { + using (var dropCommand = cn.CreateCommand()) + { + dropCommand.CommandText = $"DROP TABLE IF EXISTS [{tableName}]"; + dropCommand.ExecuteNonQuery(); + } + } + catch + { + } } } } @@ -306,38 +867,215 @@ public static void CheckLegacyNullRowVersionIsEmptyArray() { lock (s_rowVersionLock) { - Tristate originalValue = SetLegacyRowVersionNullBehavior(Tristate.True); + using SwitchesHelper helper = new(); + helper.LegacyRowVersionNullBehaviorField = SwitchesHelper.Tristate.True; + + using SqlConnection con = new(DataTestUtility.TCPConnectionString); + con.Open(); + using SqlCommand command = con.CreateCommand(); + command.CommandText = "select cast(null as rowversion) rv"; + using SqlDataReader reader = command.ExecuteReader(); + reader.Read(); + Assert.False(reader.IsDBNull(0)); + SqlBinary value = reader.GetSqlBinary(0); + Assert.False(value.IsNull); + Assert.Equal(0, value.Length); + Assert.NotNull(value.Value); + var result = reader.GetValue(0); + Assert.IsType(result); + Assert.Equal(result, reader.GetFieldValue(0)); + } + } + + + private class _Row + { + public int Id; + public Guid DocumentIdentificationId; + public string Name; + public string Value; + + public _Row(int id, Guid documentIdentificationId, string name, string value) + { + Id = id; + DocumentIdentificationId = documentIdentificationId; + Name = name; + Value = value; + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + public static async Task CanReadAwkwardDataLengths() + { + // The test data is taken from a production sample which exhibited multiple problems around async string reads. + // The lengths of the field names and data values are important to reproducing the problems. The values have been + // changed to randomized values. Changes to the column names or value lengths can cause the problems to move or + // fail to reproduce entirely because of packet and data alignment. The data is not randomly generated to help with + // debugging failures in the test. + _Row[] rows = new _Row[] + { + new _Row(27967675, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "LfcK2K8CgjnXyed7Oi", "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"), + new _Row(27967690, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "Gx1zdwPUdvrgDaIHAZSVhe5P8", "AiOiKVIsBA7NDDiNgWe93jfPHbrUOmBuOXrhLhS8lFxazaT9ZYH3IOAPCX0TpsPaT9NZ27swgOk0RLVI18piZo9uewIth5Fc2eKiuohBpfBTlkj0h4lwETcWBxor1DQ4nL6pQEzWZWNtP573KzRV1qolC1sjuXhboaXyhVJb7yCYdQyLzqpH7FS5JJx3yycATpBuIJOizvloAGIxcnnMFtghgiU7ij803xZPNgF8fQmzGEzPIxONXn7C2lOChjDjAS8T2niFA8NYoVeXG1lBcU3vLj4VURHaYriH8JYSCu3frenXmBggciQW0M2wEdqsFGyZSeEpQRmA3Exsv6tAoZ8GwhKXuIubeGi3rHkXxcI9IFUo4sNXqplkfhZnhyYQ4vMxGnTIehRHWqB5mRndlLhmHdFTo3AJ0dvJvAXpoKhnRUiEnJVJPqUSXh3zPmyHnSDvKOUDEfW3672FLO7Kzi0gQofjmPAgv9F1cVYoonBXcTqNaXKttZlr2scglmKLwqLUW5Ou7EvMnTZ6yXpZBiui8DKpy5dMUgT1KwDcAV7GK1oXTL233ZKQyrnQUS61DEJtbB6c7jaqE4QYY8hbzyUXj2MdhYrXlyP9L5KKskbha2JNsXBll44poVP0qtrMLj2jXVTTl21c9daBBKQC2NIzps3oAu7GeaGRp7MVyylkp5WLLSuowzfhsrHy0ybEaEL18GaJRIpnP9oZ9CifO2rVAqblYb3gJSjB6qS7enZZPjCFD6jY9fVGLUoshfhxQBhRyJWb2ZhaL0tyuVqNe5Q2737rIN6KrtqClXfRa0tJG42Vq9VW7852JD3KvBorptMcHxf0XFb5ESNxKikjqh7Xt6OWAFZqAa45Fv8XhLWFqVvNg2JRn6cTS7mDrPbrt5EIpTV1SpaXU3iM3jl5kdglW61CjWizxIc29n5jwauZyaOQZzhpbY9Wy8Cozyk6qPf2OXdU6D9tPueJXkNr5ffdYGYTYU7pe5i1xuF0NdXwoJMKX8mWDSqcKDYzWgOsP9WD7nD7EeksUYwlaWtOihrA3VRn705xOmtWyr26HVjOwHWWbcbHQj7K7MBL3bxZ7dPwEaYWKjUyegzjqwyRqDG2mHkkhnjwb9dfBqgtWfOgGSLblvDrGljTJJXC8IC8Cf5b3eA1fc32hzWhRD2TiE2Uq7gPRfToJE2HL7rkt9fxMXfChG8P4uiV3t9cEkRFxgMtf3UW1rvUFeBEUQTY57Sekfd7qxK1dlWTysqKDpT7Ik9DjNnvQPd8giRSo6koMnIBUarOlUHatX0WduldyQ0TdxrQquds9nAjmMnRkQ4rZrELshqAznlTgReNF8IBRKiZuj6Jnnpp4Kivs1Uc5zDH8Ntv9b2bykn4KOpYcBpiCUxclMliMjG2UxQw8BpMfguxnqjiArVWuwUdXh2knVarmO1OcxsIdVwCd4CWOklFdM1siE5swLRGkLpFXq1JFoHNOXcSUz92ZH9uk7YjIvmQDx6kqQJlzURj5wSPyy81hCXniObRSkwBL8CEEA4J8yhExLIgQMGQ7c9Fu1ZdBlfAuKIXzLO5XR2qLIlk7N5VJS3RShXTU4gbx7db9gN7pav6gV1FFmlmMQQV7YxRYHWTwezjiJLvNGd63OZqxx98Cy0UaWzd19wsrXehorswZFnkG64ZqwWPXfGIKfh9PgI54owqbmDhCx2z46QMiHZYsf3RYcAeAWzJun6WhlMDIOh5vtGFQrMUqhgSgsV0Vnda3yRo0W7HAaYsmzpkCkqkqVIWFWHENhlgGC8Suesc4SPxZJDDjkenXZHht5iBcAR7fZ0cxHTvygGVnjfKDFPmcPZO8JmUSVAgSSWDNS8M6lmihSZJ3YZ4mgXeJew6ukV5S8HWtqkxxBrzulgYh7Pc4Utde8Q2kPc4fswERTxvaCQpnRoplUYwGl9wyJlDXmrcqbNhzcgRrcuxNTfSnwaXS0KQk0G1lGYDX76eTMn2Xh679wqHAKjsnWgP45W8SkmkRYNaoICUwDZ2tJroo7QoECDu0xmDpAdX1yItUUQBmSzhiJz0fMxJytbbDhprGlXuQpk4ylJjAfhm0b2NOrTLsCNJJTjgYI6ecIb3xDTKMeJIWtLm18hSiiB6uDfTvwkobopQvuPngctOimTGKYaFZPYDszkXyDZ8eWWAjbfR6cza6otWKcPaFerCVkwhltcRzkOcZBugzXEQL5L5yu2yN12bAl1xUgckEhF8MS9Hdr49f475zIGLcB8afLPFaLa1gdzjsL5pi7qKPb6pn8Kfl378caXBMip25Qmzi1XgMYdzIILGtiXpCIUpeY3YrlMukxbi5FDJaafqrlPlIOcYtisq8SbW7mGcTxqQW2lzumUIpFvO0HSlUQO7fozrf8y5kbWkvi6MhVM1soqOs3aCQMfKoU8eFzJHzJFBjupDXjf9EhuSH0a9IuBjZnc8KsFaasnlGqVfldLNqCcASQKY66xi7S7l4upi7k6Ao7cSlbssutmQsRMCzlcdYwR91i6jNCBVlww8d2Ck9zpZUqLn7v9lu2s46BCZCwYPN1axyNc1QD9VhdTmmYVxFRPTGNLgz3IVUWQRNgyBI08WPebBBd7AeuLqU1tXzuczBNEnGX1RbkfwTE8YBbDCTOkkKGtgC8zfFQJqmSInedd7M21UR5zmjgx2xlY7O5OYtcvAh9KsaR6jvhfpo4hSe668LiKlgHrzVQjKiaG8QCPGzHdujWTVKwfAobIp7egSqWQnQIovdw7LGnq3EHRmMcSmxRYTGmyfefEK4rHYdn0OFhYQrs9k6drft6ffyy1UeRbydu8mIjs777fUPN4EIidyDdeDWZLxXCmP4zSrJVNLBll410MKa0jLaD8ARkLqWRZX1dzk7fgUzpu6AMbHjMzzCMUHEP109E1mrhJyv9ZJNEGAa0oo9QtQ7E8xhrF8Z5YkyOzExSix2s76er5D1sSHU39BLI3tYulnU8YU7WGd9tXOmUIWAHYdB1ayHfoIex7KnYxRjOsTtSlm9IXyZEdRV9ZzobPrLHPeWDLr9dm7nfjh1y6wM1jNU7C82X9T3dZZ3z5hfd0wkoZi3skmtONwyQvjgymyynrRN1ZA9kBrTUOjHZ2Ei7kby270gWYDyx9xgoE4kHfkOXuBdizM7OnWnzBwzOzzyxcHtQG6dzoBxY7dTGroOWgRtz31HeSDJEkDOVRuhVsxfFnNEepKZwUw2aR9Izlp7dJgKLD59vfwuEp0Hr22vhVRb48qTZrQjDGfYlc7Hmuqmhe1ih2LPGIbQtON8Zn50VUeTVlFZpFsd1aZpiuD1R3cBQgLyksmlt3HMIa4DWWsavD2i7ved0rYTphSyM8d1OmDyBggMnEN6qZMXpbbeO2zArPN6NaqrwBCbYghw2fbVnNw1tKdyrgjNc8IA6suGfHcPd6TGGkRUn3dXWg1iDvCi5huRPZfV274Of4I1zUvSingoXIwh4JETzTvD3afApCs3e0YcIyGDXtGUmZV847XpHfsb3L7lOoU0vAA5Kf2iNuMgMMYDDzJBvExbablsQFHCfWPFGbUctFDh23pTMZcpHDdxztFtlnfSQUfQurZOMt0wFtKl5tPaQTmfzqpZlhbF8yVqJbZIJACX2vn4rctkf8qfcN0PwIZtPVDIk7KUZ5Ad8Uc2Fkww8qKdkO82hrDhstsfrPgcKHTlEdkVLlycandGtItFYGku8vXnYJT3mEH7XrgGymtJpZqj93a4Muw0lOuNoKb8S8DJMOd0PPOXVfdLCAdFPUxfy6KhmdiCmxI4aDKgD3C30AQs8NNGWeaji7V9QaMtCYewyCL29PCyGQbW39HDY4ju8ElmljzXtvBrMFB9L7PBOtVTxXr9u5dNUzTMnp77sNy0jTgnN0oMzEEmTQu4JsjGU3LAjGX24KfvV1TZGuIwv3Yz2OBg3uAtrrrttyNfUJfkfGvHu6InvB5NyXQh5Lb2fSklnK350HeJZUPZFmt1CPJupUb3lVXQZioQlVm03sKsKp24dZ51WXCkbt4mbmKFGnDtWhRSpYP8yc3QNDiZ42zCfz26BmRTm0am6FjstN0KinnJ4b4fm2n2qCL7cCOdxqtEQSXpGRxL6cgTq5iMXr5ldj3m8cHQH2slNvpKvfHiuCi9DmEpyQpZu62HXppSaipjPqDvEFjjt38xPr2fdxF0Z8NUXR1voAqgInHE6aKHSCIWYwHgRMd2P4PzrpjSg996xlbgMvrAPbHl87z6EtOFFwgWUZTNU6wexIifEqJUMDNbPNoDyc4YyZaDp7uey934rykuP7EV30yg9TXlMd5tpan74p0lq47dn6WB7eABUBiZKOyHjNkjIreG4c3LMUUfIdDq9A98U5NpAuRdAWIEMavSnsifd8UuqGJmEQVg6m1zEeLuJkH5U9qtseSrjaE8OwAd6Ywez894XIurqbZnk1zGcLSokJUWucs2Xj6EX2ZFLbHwaQW4lG4QvsA3MdNZptOJQmMnpld6UN2DYVzkS7uCw4r5GZ9eK2udDWzXXlS0EzCanoHNO71tLTuO6EhMnGml5anOYrD4r2BO0E0WGGQoIzIytRgYtI8PEGoTKyxNr37OXoRfjj8sLgiNszOBj5kyoc36gc2Mc2qxVyP3ZtdAjtlqmJj8hW6qvk5qalN3oN8EfMvDfxRNzTVdF0z7JFlHuIFRMZFL0czABJfDTVxrZymhFZ20ujMMCJ8hIsp2xvDgve6hGXLg0CrticUP3k5FQVC4urZGBi6LJnUXemBwV8vJHJBsPCrR4XPAcVeFD29ifzjgBNIiQQMR62H25gA4Tws2ou8XN5MsgDsA6X4NBHpzDUj8Q7VEuop6uTpLu1V5HQRp3Ta0J15mYubZGaQWCdNKbc3RfXPSHClJsMhOWNnLfGoVaVNFkGWdmxzepVvgQaeVKS4V24SIgr0OmvoEzbPCwFGtGKIQqYA22kCIg6ZfAy1JpQgNb3y1PFyd9E5miGIcT37zmG43VqWA8Skpi7Y96XrlmGOoGT0JBEkXvOzzug7r4ILA4pnxKLBPtwa5j7peKmnqh1eN3PdyYtLFcku6RX9IIwDJ4J9bV7CyAWht3h8DnWK8LCXfKj5OYTIXtKNzIHFd9ZqayNW2QXhmTyBsewhAb6VJZQ7PmUTSgib5RiOXJbfKKrtTHksyFAspM011yTgwwb8QqFkqyMrsejptpTcgdbhODLXzqekqBddxtc6B7C5Qd9xBiuj9yGqALu2hYiCERcPDvJStZlex9fA1IYr9xP7DhIMH0874MOTBlJoyDRCZDlYcTVp5Vgyrm1TtzXOGEythxEuXBeUoUetrmyd1p4qzkIcXf9oxcHGLp51D02GkG3ILqTUKPFffBMlVmpEp2Sxg1vNWG0e5S1NHMRzXmhiPFmVOi0MK9Zby82pWTmYhrFkhqbu2zEk8DwPc1kH0EDY5n9N0A7afZ4BIbNwbYrgV6RC68YnJTPgelURIkWfNxH8Xfkh7Yjogc3O10Hs4SnyFXMn65Ithd2QnumKD3xW69SVDAI30suV5yBnNUVib3biC4ZDKSSUvA3L8g18zY16ov3MBxgk198ZjXuf4az5UnWufFwCRKUQitaqN1hpIrd15Q1t5JSEVbcLo4jS2AAB3RvEcbI8sfziwwVExst8k2VkR7m1hNs1yrPbzPVxuHDyhCuGrJn0LQAOU6frgfnH3KFvmQ7cOhTGjpTSuSjFoex86FC4KGTodH65r54am7bKeCq1oBSqwSyq3nAEP7VBM5KDvbHArHY6YeljPxOjk6jpW30woLPn6Msu54bz6NrYiJecATLzxZq0elgCKCeYiW0rPAJo0MWaosZmLAGe8O40FnVKNPMaQtwkYRABXtnBmMcAoBnHcDoEjXIZqfsIugLp0NsuKGGISkkD5j5PwHtZVdrSghWXNYiyH6ZeHLjW38TSjVbNJjfA0XoBvi42kZuF6Jjsvq3oSstJUm2qS4Wwp109obZQjcWUL8kb4B0FbVaWlrVIvfs9EuMbKi1AJP9XJP7cvNg1yylDv752uoOkt4UVqgYWfVK6Cu4fJZzYIeiKfYz96jr2Ta1NmOaNFYf70wkjOvFtl5kBn6oRrTaxaNWryCiSFoMm5815PM0OPLNNxFqoEZN067mj45QuRKDjlPy14oTToY5sG5QuUyKTlr8y8st0DCKpsvHnhMDVM2tEnkpxO3hZWPx9tQBbBEofwNM8FGWRIWHl6OKIJQZZHQssiXOWYoxKVXEJleLXbyt7IzbDBVSZEmHAw1o8kSs7FocpukSJ4w5IdY32zpcz7moz0WyX5e2iVWQAHrqoNCscObuS9hJlUhkL8KJ9H7YtUIDDrzr81LeS72XEdThNkjaYk8DRvVX2ycqmXyjEiHXCysreuVMCN7piD0TTcFjYGUWISnGaiWoSNI6Art4cLXkkPExrtkxdd0csTyCDJDO92ycbzmt9Mj7FjwOa7deaVvV0hub7DYQtNXSPHjdzglTDu0PRWKuCH6soU5vY9t8wEiF6LG7nyy1BwaeiOITJGABIq5pSElOsepj05WMfpIgzZxYVNLq8gOup8DGPcXkyRye6geeXvVWAkvPYmaMRZlENovZC7vArM9PKGHQTC9Z0XqwIA5Guxxr4alTz8KdsPpRN98G2j0Ur5SxggKkotvWYaeJAlwHRhoGtNSyvHpdB1jWAo4UdmtF1pWbsoK3zMiE7zgZg3NCUN7KR29C4e8Qp2C3JkUqc7YrGiicqd2f4aEZOiANLlJ9MxffRgJrAmRqWcdGJcjO9GYSIBvHicuCl3fo5PLO0oAsh3XLZjyqHP1PTxwLiBADLUJpbI0bmtSqo5HOvv1TjEoSuTzbiQTTplsrha5hKzBvsRpbCS0DdYM6ggLfgn2M51VKdVtEbxfl3ZLxHLmyxsabUu8PNRhJJtuQaqA3KKQ8murrE4ztEzGBCRwnJYOk1PQusWgKYJRNviBJYl9z6itxot4ACGoIsMKbfWIlGfNpQG5RNj0YVl13x1zMQVV4h8TPiXkbOHR4VXy6HFniJFzwMjoc0BrShKJn0EZ6nA0GsEe5XdnlLNe53vGWhksZTKS8JMCNywLBtaFhY6SDvQq7J40mKWWJnKnJRjn90pZ8fHtJapgI8HM8jryDVOOyZMmlXB2dXy7fVf9HViL8x0rgjAiEXANUFqDgACr62kEPLAFNgiRdMbT2vZBHIP4MMtbFdkrwlS01p6tQSeYYDEFqCg0NNq4CZmvdTeWVcU9DulYOVAG5kEMqNjvllgo2UkH8vUQqk2MU4KCzHe3Z4IoDaixRT9SIK8Gtl6QFKEemULiNVYPhbdF5ttfp3WFpa1lMFKzALN8MyAV75X2woMn3CUKC59cU5nrHbv"), + new _Row(27967683, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "1O7MMFFVKh7OUY8Z17pNKIhNorz", "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"), + new _Row(27967670, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "2yF66o0lSHl", "5ig"), + new _Row(27967672, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "5tvcpUkTvlRdy", "bQl"), + new _Row(27967686, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "MigVFhYMw6jG", "2ZK883Zo1CpaZH"), + new _Row(27967687, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "BqqByWX5Wuc6DooWnEC", "PPKwf6FpnJfSTklfX"), + new _Row(27967684, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "Lwt77ImTu24Fv", "1Sb8VpRuiBoqcVXblALJW"), + new _Row(27967676, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "odZDwRGrx7hRmxR", "4SMlxm6uDqztEaWm5KgRstymBQh5yF8fLtXo"), + new _Row(27967677, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "Y2dvJ9z80n0ri9hR", "dhq5"), + new _Row(27967689, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "Dhhd7cliCo2E8Yv1FIK", "B1VC"), + new _Row(27967674, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "sy79bLYyrZ1", "t50JSsD"), + new _Row(27967673, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "37Wh8RDNCMh", "C4u4MFMySkFV"), + new _Row(27967671, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "iCQZR01", "GoODhGbRzTKk7qBWjCc2"), + new _Row(27967685, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "M8xQrKB8yEOLVGR", "LDJMXhpJvfGFKNjT3JDBatO"), + new _Row(27967666, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "FfrqRLP", null), + new _Row(27967669, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "QXSN3", "XCtCRP"), + new _Row(27967668, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "LAnC6", "Z4gmN"), + new _Row(27967667, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "fiE2vDYS7", "cB6hHKmoBiMa0dRG"), + new _Row(27967688, new Guid("58EE5163-5F41-4C83-8175-34580FA54BDE"), "YJxqBBgPfdgqH26XCUWD0gHCsv", "ZyhKMi2fBWVrNdy44Z5JIZK"), + new _Row(27942037, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "LesQjDrs6rjgDj4MaD", "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"), + new _Row(27942047, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "ho3Pv3aLmCusWltGlgEAbwwpH", "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"), + new _Row(27942040, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "uARU2FodUEIb3ixMr401zs5gGq3", "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"), + new _Row(27942032, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "UTaQip0jFLV", "6MF"), + new _Row(27942034, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "5b8zlELFQw1rW", "7Y9"), + new _Row(27942043, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "IriW7xhF2aor", "lvxx7PedzFWUBi"), + new _Row(27942044, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "u8JMj7ChBMeJnN98eOt", "ITrPShAjSMoCTMfMj"), + new _Row(27942041, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "ugXGwPnQblwxd", "tiddVfujPuz0HKaTXQLMe"), + new _Row(27942038, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "Kxl8aC5uKR0zVJl", "QPZzAKNQjt2Dm6glZCdxvGBzpVPi9BMAVv4e"), + new _Row(27942039, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "3B5ikbV50KkZ5Ffs", "aRhr"), + new _Row(27942046, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "Nw6iJxi2wWKA8twN2wO", "7RY0"), + new _Row(27942036, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "Fi4DiLijhnY", "mNhLjRF"), + new _Row(27942035, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "uuzw6oLPddS", "0ADpjuhWDHJj"), + new _Row(27942033, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "rLxgGM6", "JHMtkwRTVoTebnaPLUUY"), + new _Row(27942042, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "U4crJOexXz6N0dx", "ZKnTZYlikloyaQiVWvYlbkp"), + new _Row(27942028, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "j4Ipsks", null), + new _Row(27942031, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "PU6Yf", "UxStUM"), + new _Row(27942030, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "v1wYY", "6HQA0"), + new _Row(27942029, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "t2xP2OnIo", "xl8Kc65mfbv4PX"), + new _Row(27942045, new Guid("08E06BB1-757A-4D8B-9768-4966D16F9173"), "IhJwQDjAVByAKvnYVJd1fseUvD", "wZBP5xrHTmSd4zF8vfVXfw5"), + new _Row(27939369, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "5FEOfqCx1nSxMubDot", "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"), + new _Row(27939379, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "Eqc2KebZ3jFKtPXOCZtiNMJMq", "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"), + new _Row(27939372, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "h9zfijozkzKtEN0oIWWKtg1d37r", "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"), + new _Row(27939364, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "pl4SEzxt5aj", "y2i"), + new _Row(27939366, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "pHu8rACwoAeAR", "6P5"), + new _Row(27939375, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "I5QPIDh0pOQq", "TyN7SRqp1e9xhN"), + new _Row(27939376, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "tIs1cRUF6EQXIZLH5qL", "WdgCal1kzJsBfiLYg"), + new _Row(27939373, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "7AAFQU8b5xQjz", "48YSumTpuwo90lHwHmcuW"), + new _Row(27939370, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "bUkjxvr2Ij6Cb7I", "bQ4bNrK4yQHdJ9WTB5O8JxitdW24vEoHFhii"), + new _Row(27939371, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "1aUWwkCStvQ0aa9H", "PKjk"), + new _Row(27939378, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "JHNBoMSpcgbYBwqCTja", "0pUE"), + new _Row(27939368, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "VETrliW5qrS", "W7VBDVZ"), + new _Row(27939367, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "DZpLEj7a7x8", "fske7dTzHB0V"), + new _Row(27939365, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "BFiGUeE", "6FfoJN00SRpBwaeJMzka"), + new _Row(27939374, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "0VOL3LAUw09go4n", "joEoLSBPGNTxv4JSAV7YqRo"), + new _Row(27939360, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "YjKaV7r", null), + new _Row(27939363, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "fujpL", "5d3m8k"), + new _Row(27939362, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "0nZnP", "zCOIN"), + new _Row(27939361, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "IImbQh9yt", "8fh5FpTwXX0CT"), + new _Row(27939377, new Guid("E8F87ACF-C537-4347-9BC4-CDF125E759AC"), "nte3hSSnA5qUDrTeBps0GajHFV", "b7yn1aDZ0PopOOwVSSFwEZQ"), + new _Row(27967700, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "6PsGeEZLzUcnKGKL0O", "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"), + new _Row(27967710, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "4UemQLTq1dAnEVC19A6BwVHaG", "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"), + new _Row(27967703, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "PztxRBuJCcvIULKp6mUwHuyHQHH", "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"), + new _Row(27967695, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "ZOySIGfxJTT", "VRY"), + new _Row(27967697, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "Qx3mI5MQCPF9k", "xVy"), + new _Row(27967706, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "01TPl2U9dzNa", "bwDlLckZEHqokP"), + new _Row(27967707, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "jgp5sCGrgL08A5QGfM5", "OM1DT9iSMoVZRMIGu"), + new _Row(27967704, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "ffXeEmkW5ZiRP", "Hr9e5U4qbWHpPxNljTYUz"), + new _Row(27967701, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "D83pFinFz60LiQd", "z99W2HKNyBShCLiqWrl3oKiOYmhK6TlvlxSC"), + new _Row(27967702, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "h8kGa6byq7u6xwwH", "dFXM"), + new _Row(27967709, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "dQTHfJT3W0ZoVvphWcL", "i3sH"), + new _Row(27967699, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "iNlhTgyYc4f", "BJkaAdh"), + new _Row(27967698, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "jst3eaOfwwy", "z6wGAgKIcwOp"), + new _Row(27967696, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "GE5DZYd", "NyFnZKT73M4QV5Ih3puI"), + new _Row(27967705, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "jf2cClMdD5wiEli", "M9ovCXpQGbLw52b4jlVS9eK"), + new _Row(27967691, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "tJXdP5Q", "boSRIr1E8Eqe8BCPCdYDi"), + new _Row(27967694, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "kmuCP", "2VzY"), + new _Row(27967693, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "cLc27", "ujAix"), + new _Row(27967692, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "UUHWcCtTe", "WAqv8AbdDPoSlh"), + new _Row(27967708, new Guid("C214F6BB-53EB-4858-A944-D227F9A2846F"), "1hlglR0vEPdA3ZxqVI3E59targ", "9yl8QMmpLjCGS8F2ETvyCeo"), + }; + + using (var cn = new Microsoft.Data.SqlClient.SqlConnection(DataTestUtility.TCPConnectionString)) + { + await cn.OpenAsync(); + + string tableName = DataTestUtility.GenerateObjectName(); + try { - using SqlConnection con = new(DataTestUtility.TCPConnectionString); - con.Open(); - using SqlCommand command = con.CreateCommand(); - command.CommandText = "select cast(null as rowversion) rv"; - using SqlDataReader reader = command.ExecuteReader(); - reader.Read(); - Assert.False(reader.IsDBNull(0)); - SqlBinary value = reader.GetSqlBinary(0); - Assert.False(value.IsNull); - Assert.Equal(0, value.Length); - Assert.NotNull(value.Value); - var result = reader.GetValue(0); - Assert.IsType(result); - Assert.Equal(result, reader.GetFieldValue(0)); + using (var cmd = cn.CreateCommand()) + { + cmd.CommandText = $""" + create table [{tableName}] + ( + [Id] [int] NOT NULL, + [DocumentIdentificationId] [uniqueidentifier] NOT NULL, + [Name] [nvarchar](40) NULL, + [Value] [nvarchar](max) NULL + ) + """; + + await cmd.ExecuteNonQueryAsync(); + + cmd.CommandText = $"INSERT INTO [{tableName}] VALUES (@id,@docId,@name,@value)"; + SqlParameter id = cmd.Parameters.AddWithValue("@id", 0); + SqlParameter docId = cmd.Parameters.AddWithValue("@docId", Guid.Empty); + SqlParameter name = cmd.Parameters.AddWithValue("@name", ""); + SqlParameter value = cmd.Parameters.AddWithValue("@value", ""); + + foreach (var row in rows) + { + id.Value = row.Id; + docId.Value = row.DocumentIdentificationId; + name.Value = row.Name; + value.Value = row.Value == null ? DBNull.Value : row.Value; + await cmd.ExecuteNonQueryAsync(); + } + } + + int counter = 0; + while (counter < 10) + { + using (var cmd = cn.CreateCommand()) + { + cmd.CommandText = $"SELECT [d].[Id], [d].[DocumentIdentificationId], [d].[Name], [d].[Value] FROM [{tableName}] AS [d]"; + using (var reader = await cmd.ExecuteReaderAsync()) + { + int row = 0; + while (await reader.ReadAsync()) + { + int id = await reader.GetFieldValueAsync(0, default); + Guid docId = await reader.GetFieldValueAsync(1, default); + string name = await reader.GetFieldValueAsync(2, default); + string value = null; + if (!await reader.IsDBNullAsync(3, default)) + { + value = await reader.GetFieldValueAsync(3, default); + } + + Assert.Equal(id, rows[row].Id); + Assert.Equal(docId, rows[row].DocumentIdentificationId); + Assert.Equal(name, rows[row].Name); + Assert.Equal(value, rows[row].Value); + row += 1; + } + } + } + counter++; + } } finally { - SetLegacyRowVersionNullBehavior(originalValue); + try + { + DataTestUtility.DropTable(cn, tableName); + } + catch + { + } } } } - - private static Tristate SetLegacyRowVersionNullBehavior(Tristate value) - { - Type switchesType = typeof(SqlCommand).Assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches"); - FieldInfo switchField = switchesType.GetField("s_legacyRowVersionNullBehavior", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); - Tristate originalValue = (Tristate)switchField.GetValue(null); - switchField.SetValue(null, value); - return originalValue; - } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs index 7fda024ed5..ee9c0ed4cb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs @@ -1913,12 +1913,14 @@ private static void VariantCollationsTest(string connectionString) private static void TestXEventsStreaming(string connectionString) { - string sessionName = DataTestUtility.GenerateRandomCharacters("Session"); - - try + // Create XEvent + using (SqlConnection xEventManagementConnection = new SqlConnection(connectionString)) + using (DataTestUtility.XEventScope xEventScope = new DataTestUtility.XEventScope(xEventManagementConnection, + "ADD EVENT sqlserver.user_event(ACTION(package0.event_sequence))", + "ADD TARGET package0.ring_buffer")) { - //Create XEvent - SetupXevent(connectionString, sessionName); + string sessionName = xEventScope.SessionName; + Task.Factory.StartNew(() => { // Read XEvents @@ -1957,52 +1959,6 @@ private static void TestXEventsStreaming(string connectionString) } }).Wait(10000); } - finally - { - //Delete XEvent - DeleteXevent(connectionString, sessionName); - } - } - - private static void SetupXevent(string connectionString, string sessionName) - { - string xEventCreateAndStartCommandText = @"CREATE EVENT SESSION [" + sessionName + @"] ON SERVER - ADD EVENT sqlserver.user_event(ACTION(package0.event_sequence)) - ADD TARGET package0.ring_buffer - WITH ( - MAX_MEMORY=4096 KB, - EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS, - MAX_DISPATCH_LATENCY=30 SECONDS, - MAX_EVENT_SIZE=0 KB, - MEMORY_PARTITION_MODE=NONE, - TRACK_CAUSALITY=ON, - STARTUP_STATE=OFF) - - ALTER EVENT SESSION [" + sessionName + "] ON SERVER STATE = START "; - - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - using (SqlCommand createXeventSession = new SqlCommand(xEventCreateAndStartCommandText, connection)) - { - createXeventSession.ExecuteNonQuery(); - } - } - } - - private static void DeleteXevent(string connectionString, string sessionName) - { - string deleteXeventSessionCommand = $"IF EXISTS (select * from sys.server_event_sessions where name ='{sessionName}')" + - $" DROP EVENT SESSION [{sessionName}] ON SERVER"; - - using (SqlConnection connection = new SqlConnection(connectionString)) - { - connection.Open(); - using (SqlCommand deleteXeventSession = new SqlCommand(deleteXeventSessionCommand, connection)) - { - deleteXeventSession.ExecuteNonQuery(); - } - } } private static void TimeoutDuringReadAsyncWithClosedReaderTest(string connectionString) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ConnectionExceptionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ConnectionExceptionTest.cs index b1ba557562..6ee0681a0d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ConnectionExceptionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ExceptionTest/ConnectionExceptionTest.cs @@ -19,6 +19,7 @@ public class ConnectionExceptionTest private const string orderIdQuery = "select orderid from orders where orderid < 10250"; private static bool IsNotKerberos() => DataTestUtility.IsKerberosTest != true; + [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3031")] [ConditionalFact(nameof(IsNotKerberos))] public void TestConnectionStateWithErrorClass20() { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs index ae4074c1e5..02133e0df4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/InstanceNameTest/InstanceNameTest.cs @@ -160,9 +160,9 @@ private static string GetSPNInfo(string dataSource, string inInstanceName) { Assembly sqlConnectionAssembly = Assembly.GetAssembly(typeof(SqlConnection)); - Type sniProxyType = sqlConnectionAssembly.GetType("Microsoft.Data.SqlClient.SNI.SNIProxy"); - Type ssrpType = sqlConnectionAssembly.GetType("Microsoft.Data.SqlClient.SNI.SSRP"); - Type dataSourceType = sqlConnectionAssembly.GetType("Microsoft.Data.SqlClient.SNI.DataSource"); + Type sniProxyType = sqlConnectionAssembly.GetType("Microsoft.Data.SqlClient.ManagedSni.SniProxy"); + Type ssrpType = sqlConnectionAssembly.GetType("Microsoft.Data.SqlClient.ManagedSni.SsrpClient"); + Type dataSourceType = sqlConnectionAssembly.GetType("Microsoft.Data.SqlClient.ManagedSni.DataSource"); Type timeoutTimerType = sqlConnectionAssembly.GetType("Microsoft.Data.ProviderBase.TimeoutTimer"); Type[] dataSourceConstructorTypesArray = new Type[] { typeof(string) }; @@ -212,9 +212,9 @@ private static string GetSPNInfo(string dataSource, string inInstanceName) string serverSPN = ""; MethodInfo getSqlServerSPNs = sniProxyObj.GetType().GetMethod("GetSqlServerSPNs", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, CallingConventions.Any, getSqlServerSPNsTypesArray, null); - byte[][] result = (byte[][])getSqlServerSPNs.Invoke(sniProxyObj, new object[] { dataSrcInfo, serverSPN }); + object resolvedSpns = getSqlServerSPNs.Invoke(sniProxyObj, new object[] { dataSrcInfo, serverSPN }); - string spnInfo = Encoding.Unicode.GetString(result[0]); + string spnInfo = (string)resolvedSpns.GetType().GetProperty("Primary", BindingFlags.Instance | BindingFlags.Public).GetValue(resolvedSpns); return spnInfo; } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonBulkCopyTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonBulkCopyTest.cs index 7cb3044e6a..c92ba237b4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonBulkCopyTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonBulkCopyTest.cs @@ -2,35 +2,47 @@ using System.Collections.Generic; using System.Data; using System.IO; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Newtonsoft.Json.Linq; using Newtonsoft.Json; using Xunit.Abstractions; using Xunit; +using System.Collections; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.JsonTest { public class JsonBulkCopyTest { private readonly ITestOutputHelper _output; - private static readonly string _jsonFile = "randomRecords.json"; - private static readonly string _outputFile = "serverRecords.json"; + private static readonly string _generatedJsonFile = DataTestUtility.GenerateRandomCharacters("randomRecords"); + private static readonly string _outputFile = DataTestUtility.GenerateRandomCharacters("serverResults"); + private static readonly string _sourceTableName = DataTestUtility.GenerateObjectName(); + private static readonly string _destinationTableName = DataTestUtility.GenerateObjectName(); public JsonBulkCopyTest(ITestOutputHelper output) { _output = output; } - private void PopulateData(int noOfRecords) + public static IEnumerable JsonBulkCopyTestData() + { + yield return new object[] { CommandBehavior.Default, false, 300, 100 }; + yield return new object[] { CommandBehavior.Default, true, 300, 100 }; + yield return new object[] { CommandBehavior.SequentialAccess, false, 300, 100 }; + yield return new object[] { CommandBehavior.SequentialAccess, true, 300, 100 }; + } + + private void PopulateData(int noOfRecords, int rows) { using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) { - DataTestUtility.CreateTable(connection, "jsonTab", "(data json)"); - DataTestUtility.CreateTable(connection, "jsonTabCopy", "(data json)"); - GenerateJsonFile(50000, _jsonFile); - StreamJsonFileToServer(connection); + DataTestUtility.CreateTable(connection, _sourceTableName, "(data json)"); + DataTestUtility.CreateTable(connection, _destinationTableName, "(data json)"); + GenerateJsonFile(noOfRecords, _generatedJsonFile); + while (rows-- > 0) + { + StreamJsonFileToServer(connection); + } } } @@ -59,7 +71,7 @@ private void GenerateJsonFile(int noOfRecords, string filename) private void CompareJsonFiles() { - using (var stream1 = File.OpenText(_jsonFile)) + using (var stream1 = File.OpenText(_generatedJsonFile)) using (var stream2 = File.OpenText(_outputFile)) using (var reader1 = new JsonTextReader(stream1)) using (var reader2 = new JsonTextReader(stream2)) @@ -70,14 +82,14 @@ private void CompareJsonFiles() } } - private void PrintJsonDataToFile(SqlConnection connection) + private void PrintJsonDataToFileAndCompare(SqlConnection connection) { - DeleteFile(_outputFile); - using (SqlCommand command = new SqlCommand("SELECT [data] FROM [jsonTabCopy]", connection)) + try { - using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.SequentialAccess)) + DeleteFile(_outputFile); + using (SqlCommand command = new SqlCommand("SELECT [data] FROM [" + _destinationTableName + "]", connection)) { - using (StreamWriter sw = new StreamWriter(_outputFile)) + using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.SequentialAccess)) { while (reader.Read()) { @@ -86,28 +98,36 @@ private void PrintJsonDataToFile(SqlConnection connection) using (TextReader data = reader.GetTextReader(0)) { - do + using (StreamWriter sw = new StreamWriter(_outputFile)) { - charsRead = data.Read(buffer, 0, buffer.Length); - sw.Write(buffer, 0, charsRead); + do + { + charsRead = data.Read(buffer, 0, buffer.Length); + sw.Write(buffer, 0, charsRead); - } while (charsRead > 0); + } while (charsRead > 0); + } } - _output.WriteLine("Output written to " + _outputFile); + CompareJsonFiles(); } } } } + finally + { + DeleteFile(_outputFile); + } + } - private async Task PrintJsonDataToFileAsync(SqlConnection connection) + private async Task PrintJsonDataToFileAndCompareAsync(SqlConnection connection) { - DeleteFile(_outputFile); - using (SqlCommand command = new SqlCommand("SELECT [data] FROM [jsonTab]", connection)) + try { - using (SqlDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess)) + DeleteFile(_outputFile); + using (SqlCommand command = new SqlCommand("SELECT [data] FROM [" + _destinationTableName + "]", connection)) { - using (StreamWriter sw = new StreamWriter(_outputFile)) + using (SqlDataReader reader = await command.ExecuteReaderAsync(CommandBehavior.SequentialAccess)) { while (await reader.ReadAsync()) { @@ -116,25 +136,32 @@ private async Task PrintJsonDataToFileAsync(SqlConnection connection) using (TextReader data = reader.GetTextReader(0)) { - do + using (StreamWriter sw = new StreamWriter(_outputFile)) { - charsRead = await data.ReadAsync(buffer, 0, buffer.Length); - await sw.WriteAsync(buffer, 0, charsRead); + do + { + charsRead = await data.ReadAsync(buffer, 0, buffer.Length); + await sw.WriteAsync(buffer, 0, charsRead); - } while (charsRead > 0); + } while (charsRead > 0); + } } - _output.WriteLine("Output written to file " + _outputFile); + CompareJsonFiles(); } } } } + finally + { + DeleteFile(_outputFile); + } } private void StreamJsonFileToServer(SqlConnection connection) { - using (SqlCommand cmd = new SqlCommand("INSERT INTO [jsonTab] (data) VALUES (@jsondata)", connection)) + using (SqlCommand cmd = new SqlCommand("INSERT INTO [" + _sourceTableName + "] (data) VALUES (@jsondata)", connection)) { - using (StreamReader jsonFile = File.OpenText(_jsonFile)) + using (StreamReader jsonFile = File.OpenText(_generatedJsonFile)) { cmd.Parameters.Add("@jsondata", Microsoft.Data.SqlDbTypeExtensions.Json, -1).Value = jsonFile; cmd.ExecuteNonQuery(); @@ -144,9 +171,9 @@ private void StreamJsonFileToServer(SqlConnection connection) private async Task StreamJsonFileToServerAsync(SqlConnection connection) { - using (SqlCommand cmd = new SqlCommand("INSERT INTO [jsonTab] (data) VALUES (@jsondata)", connection)) + using (SqlCommand cmd = new SqlCommand("INSERT INTO [" + _sourceTableName + "] (data) VALUES (@jsondata)", connection)) { - using (StreamReader jsonFile = File.OpenText(_jsonFile)) + using (StreamReader jsonFile = File.OpenText(_generatedJsonFile)) { cmd.Parameters.Add("@jsondata", Microsoft.Data.SqlDbTypeExtensions.Json, -1).Value = jsonFile; await cmd.ExecuteNonQueryAsync(); @@ -162,15 +189,15 @@ private void DeleteFile(string filename) } } - private void BulkCopyData(CommandBehavior cb, bool enableStraming) + private void BulkCopyData(CommandBehavior cb, bool enableStraming, int expectedTransferCount) { using (SqlConnection sourceConnection = new SqlConnection(DataTestUtility.TCPConnectionString)) { sourceConnection.Open(); - SqlCommand commandRowCount = new SqlCommand("SELECT COUNT(*) FROM " + "dbo.jsonTabCopy;", sourceConnection); + SqlCommand commandRowCount = new SqlCommand("SELECT COUNT(*) FROM " + _destinationTableName, sourceConnection); long countStart = System.Convert.ToInt32(commandRowCount.ExecuteScalar()); _output.WriteLine("Starting row count = {0}", countStart); - SqlCommand commandSourceData = new SqlCommand("SELECT data FROM dbo.jsonTab;", sourceConnection); + SqlCommand commandSourceData = new SqlCommand("SELECT data FROM " + _sourceTableName, sourceConnection); SqlDataReader reader = commandSourceData.ExecuteReader(cb); using (SqlConnection destinationConnection = new SqlConnection(DataTestUtility.TCPConnectionString)) { @@ -178,7 +205,7 @@ private void BulkCopyData(CommandBehavior cb, bool enableStraming) using (SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection)) { bulkCopy.EnableStreaming = enableStraming; - bulkCopy.DestinationTableName = "dbo.jsonTabCopy"; + bulkCopy.DestinationTableName = _destinationTableName; try { bulkCopy.WriteToServer(reader); @@ -195,19 +222,20 @@ private void BulkCopyData(CommandBehavior cb, bool enableStraming) long countEnd = System.Convert.ToInt32(commandRowCount.ExecuteScalar()); _output.WriteLine("Ending row count = {0}", countEnd); _output.WriteLine("{0} rows were added.", countEnd - countStart); + Assert.Equal(expectedTransferCount, countEnd - countStart); } } } - private async Task BulkCopyDataAsync(CommandBehavior cb, bool enableStraming) + private async Task BulkCopyDataAsync(CommandBehavior cb, bool enableStraming, int expectedTransferCount) { using (SqlConnection sourceConnection = new SqlConnection(DataTestUtility.TCPConnectionString)) { await sourceConnection.OpenAsync(); - SqlCommand commandRowCount = new SqlCommand("SELECT COUNT(*) FROM " + "dbo.jsonTabCopy;", sourceConnection); + SqlCommand commandRowCount = new SqlCommand("SELECT COUNT(*) FROM " + _destinationTableName, sourceConnection); long countStart = System.Convert.ToInt32(await commandRowCount.ExecuteScalarAsync()); _output.WriteLine("Starting row count = {0}", countStart); - SqlCommand commandSourceData = new SqlCommand("SELECT data FROM dbo.jsonTab;", sourceConnection); + SqlCommand commandSourceData = new SqlCommand("SELECT data FROM " + _sourceTableName, sourceConnection); SqlDataReader reader = await commandSourceData.ExecuteReaderAsync(cb); using (SqlConnection destinationConnection = new SqlConnection(DataTestUtility.TCPConnectionString)) { @@ -215,7 +243,7 @@ private async Task BulkCopyDataAsync(CommandBehavior cb, bool enableStraming) using (SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection)) { bulkCopy.EnableStreaming = enableStraming; - bulkCopy.DestinationTableName = "dbo.jsonTabCopy"; + bulkCopy.DestinationTableName = _destinationTableName; try { await bulkCopy.WriteToServerAsync(reader); @@ -232,44 +260,51 @@ private async Task BulkCopyDataAsync(CommandBehavior cb, bool enableStraming) long countEnd = System.Convert.ToInt32(await commandRowCount.ExecuteScalarAsync()); _output.WriteLine("Ending row count = {0}", countEnd); _output.WriteLine("{0} rows were added.", countEnd - countStart); + Assert.Equal(expectedTransferCount, countEnd - countStart); } } } - [InlineData(CommandBehavior.Default, false)] - [InlineData(CommandBehavior.Default, true)] - [InlineData(CommandBehavior.SequentialAccess, false)] - [InlineData(CommandBehavior.SequentialAccess, true)] [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] - public void TestJsonBulkCopy(CommandBehavior cb, bool enableStraming) + [MemberData( + nameof(JsonBulkCopyTestData) +#if NETFRAMEWORK + // .NET Framework puts system enums in something called the Global + // Assembly Cache (GAC), and xUnit refuses to serialize enums that + // live there. So for .NET Framework, we disable enumeration of the + // test data to avoid warnings on the console when running tests. + , DisableDiscoveryEnumeration = true +#endif + )] + public void TestJsonBulkCopy(CommandBehavior cb, bool enableStraming, int jsonArrayElements, int rows) { - PopulateData(10000); + PopulateData(jsonArrayElements, rows); using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) { - BulkCopyData(cb, enableStraming); + BulkCopyData(cb, enableStraming, rows); connection.Open(); - PrintJsonDataToFile(connection); - CompareJsonFiles(); - DeleteFile(_jsonFile); + PrintJsonDataToFileAndCompare(connection); + DeleteFile(_generatedJsonFile); DeleteFile(_outputFile); } } - [InlineData(CommandBehavior.Default, false)] - [InlineData(CommandBehavior.Default, true)] - [InlineData(CommandBehavior.SequentialAccess, false)] - [InlineData(CommandBehavior.SequentialAccess, true)] [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] - public async Task TestJsonBulkCopyAsync(CommandBehavior cb, bool enableStraming) + [MemberData( + nameof(JsonBulkCopyTestData) +#if NETFRAMEWORK + , DisableDiscoveryEnumeration = true +#endif + )] + public async Task TestJsonBulkCopyAsync(CommandBehavior cb, bool enableStraming, int jsonArrayElements, int rows) { - PopulateData(10000); + PopulateData(jsonArrayElements, rows); using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) { - await BulkCopyDataAsync(cb, enableStraming); + await BulkCopyDataAsync(cb, enableStraming, rows); await connection.OpenAsync(); - await PrintJsonDataToFileAsync(connection); - CompareJsonFiles(); - DeleteFile(_jsonFile); + await PrintJsonDataToFileAndCompareAsync(connection); + DeleteFile(_generatedJsonFile); DeleteFile(_outputFile); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs index a0f02b9c20..ccf0c00919 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonTest.cs @@ -4,12 +4,13 @@ using System; using System.Data; -using Microsoft.Data; using System.Data.Common; using System.Data.SqlTypes; using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; +using System.Text.Json; +using Microsoft.Data.SqlTypes; namespace Microsoft.Data.SqlClient.ManualTesting.Tests { @@ -21,8 +22,8 @@ public JsonTest(ITestOutputHelper output) { _output = output; } - - private static readonly string jsonDataString = "[{\"name\":\"Dave\",\"skills\":[\"Python\"]},{\"name\":\"Ron\",\"surname\":\"Peter\"}]"; + + private static readonly string JsonDataString = "[{\"name\":\"Dave\",\"skills\":[\"Python\"]},{\"name\":\"Ron\",\"surname\":\"Peter\"}]"; private void ValidateRowsAffected(int rowsAffected) { @@ -36,7 +37,7 @@ private void ValidateRows(SqlDataReader reader) { string jsonData = reader.GetString(0); _output.WriteLine(jsonData); - Assert.Equal(jsonDataString, jsonData); + Assert.Equal(JsonDataString, jsonData); } } @@ -46,7 +47,7 @@ private async Task ValidateRowsAsync(SqlDataReader reader) { string jsonData = reader.GetString(0); _output.WriteLine(jsonData); - Assert.Equal(jsonDataString, jsonData); + Assert.Equal(JsonDataString, jsonData); } } @@ -75,8 +76,8 @@ private void ValidateNullJson(SqlDataReader reader) [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] public void TestJsonWrite() { - string tableName = "jsonWriteTest"; - string spName = "spJsonWriteTest"; + string tableName = DataTestUtility.GenerateObjectName(); + string spName = DataTestUtility.GenerateObjectName(); string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; string spCreate = "CREATE PROCEDURE " + spName + " (@jsonData json) AS " + tableInsert; @@ -101,13 +102,13 @@ public void TestJsonWrite() //Test 1 //Write json value using a parameterized query - parameter.Value = jsonDataString; + parameter.Value = JsonDataString; int rowsAffected = command.ExecuteNonQuery(); ValidateRowsAffected(rowsAffected); //Test 2 //Write a SqlString type as json - parameter.Value = new SqlString(jsonDataString); + parameter.Value = new SqlString(JsonDataString); int rowsAffected2 = command.ExecuteNonQuery(); ValidateRowsAffected(rowsAffected2); @@ -118,12 +119,18 @@ public void TestJsonWrite() command2.CommandText = spName; command2.CommandType = CommandType.StoredProcedure; var parameter2 = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - parameter2.Value = jsonDataString; + parameter2.Value = JsonDataString; command2.Parameters.Add(parameter2); int rowsAffected3 = command2.ExecuteNonQuery(); ValidateRowsAffected(rowsAffected3); } + //Test 4 + // Write json value using a parameterized query with SqlJson type + parameter.Value = new SqlJson(JsonDataString); + int rowsAffected4 = command.ExecuteNonQuery(); + ValidateRowsAffected(rowsAffected4); + DataTestUtility.DropTable(connection, tableName); DataTestUtility.DropStoredProcedure(connection, spName); } @@ -133,8 +140,8 @@ public void TestJsonWrite() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] public async Task TestJsonWriteAsync() { - string tableName = "jsonWriteTest"; - string spName = "spJsonWriteTest"; + string tableName = DataTestUtility.GenerateObjectName(); + string spName = DataTestUtility.GenerateObjectName(); string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; string spCreate = "CREATE PROCEDURE " + spName + " (@jsonData json) AS " + tableInsert; @@ -159,13 +166,13 @@ public async Task TestJsonWriteAsync() //Test 1 //Write json value using a parameterized query - parameter.Value = jsonDataString; + parameter.Value = JsonDataString; int rowsAffected = await command.ExecuteNonQueryAsync(); ValidateRowsAffected(rowsAffected); //Test 2 //Write a SqlString type as json - parameter.Value = new SqlString(jsonDataString); + parameter.Value = new SqlString(JsonDataString); int rowsAffected2 = await command.ExecuteNonQueryAsync(); ValidateRowsAffected(rowsAffected2); @@ -176,12 +183,18 @@ public async Task TestJsonWriteAsync() command2.CommandText = spName; command2.CommandType = CommandType.StoredProcedure; var parameter2 = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - parameter2.Value = jsonDataString; + parameter2.Value = JsonDataString; command2.Parameters.Add(parameter2); int rowsAffected3 = await command.ExecuteNonQueryAsync(); ValidateRowsAffected(rowsAffected3); } + //Test 4 + // Write json value using a parameterized query with SqlJson type + parameter.Value = new SqlJson(JsonDataString); + int rowsAffected4 = await command.ExecuteNonQueryAsync(); + ValidateRowsAffected(rowsAffected4); + DataTestUtility.DropTable(connection, tableName); DataTestUtility.DropStoredProcedure(connection, spName); } @@ -191,8 +204,8 @@ public async Task TestJsonWriteAsync() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] public void TestJsonRead() { - string tableName = "jsonReadTest"; - string spName = "spJsonReadTest"; + string tableName = DataTestUtility.GenerateObjectName(); + string spName = DataTestUtility.GenerateObjectName(); string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; string tableRead = "SELECT * FROM " + tableName; @@ -215,7 +228,7 @@ public void TestJsonRead() //This will be used for reading command.CommandText = tableInsert; var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - parameter.Value = jsonDataString; + parameter.Value = JsonDataString; command.Parameters.Add(parameter); command.ExecuteNonQuery(); @@ -250,8 +263,8 @@ public void TestJsonRead() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] public async Task TestJsonReadAsync() { - string tableName = "jsonReadTest"; - string spName = "spJsonReadTest"; + string tableName = DataTestUtility.GenerateObjectName(); + string spName = DataTestUtility.GenerateObjectName(); string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; string tableRead = "SELECT * FROM " + tableName; @@ -274,7 +287,7 @@ public async Task TestJsonReadAsync() //This will be used for reading command.CommandText = tableInsert; var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); - parameter.Value = jsonDataString; + parameter.Value = JsonDataString; command.Parameters.Add(parameter); await command.ExecuteNonQueryAsync(); @@ -309,7 +322,7 @@ public async Task TestJsonReadAsync() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] public void TestNullJson() { - string tableName = "jsonTest"; + string tableName = DataTestUtility.GenerateObjectName(); string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; string tableRead = "SELECT * FROM " + tableName; @@ -336,5 +349,161 @@ public void TestNullJson() reader.Close(); DataTestUtility.DropTable(connection, tableName); } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] + public void TestJsonAPIs() + { + string tableName = DataTestUtility.GenerateObjectName(); + string tableInsert = "INSERT INTO " + tableName + " VALUES (@jsonData)"; + string tableRead = "SELECT * FROM " + tableName; + + using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + connection.Open(); + using (SqlCommand command = connection.CreateCommand()) + { + try + { + // Create Table + DataTestUtility.CreateTable(connection, tableName, "(Data json)"); + + //Insert + command.CommandText = tableInsert; + var parameter = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json); + parameter.Value = JsonDataString; + command.Parameters.Add(parameter); + command.ExecuteNonQuery(); + + // Query the table + command.CommandText = tableRead; + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + string data = reader.GetFieldValue(0); + Assert.Equal(JsonDataString, data); + JsonDocument jsonDocument = reader.GetFieldValue(0); + Assert.Equal(JsonDataString, jsonDocument.RootElement.ToString()); + Assert.Equal("json", reader.GetDataTypeName(0)); + Assert.Equal("System.String", reader.GetFieldType(0).ToString()); + Assert.Equal(JsonDataString, reader.GetSqlJson(0).Value); + } + } + } + finally + { + DataTestUtility.DropTable(connection, tableName); + } + } + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] + public void TestJsonWithMARS() + { + string table1Name = DataTestUtility.GenerateObjectName(); + string table2Name = DataTestUtility.GenerateObjectName(); + + using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString + "MultipleActiveResultSets=True;")) + { + connection.Open(); + try + { + // Create Table + DataTestUtility.CreateTable(connection, table1Name, "(Data json)"); + DataTestUtility.CreateTable(connection, table2Name, "(Id int, Data json)"); + + // Insert Data + string table1Insert = "INSERT INTO " + table1Name + " VALUES ('" + JsonDataString + "')"; + string table2Insert = "INSERT INTO " + table2Name + " VALUES (1,'" + JsonDataString + "')"; + using (SqlCommand command = connection.CreateCommand()) + { + command.CommandText = table1Insert; + command.ExecuteNonQuery(); + command.CommandText = table2Insert; + command.ExecuteNonQuery(); + } + + // Read Data + using (SqlCommand command1 = new SqlCommand("select * from " + table1Name, connection)) + using (SqlCommand command2 = new SqlCommand("select * from " + table2Name, connection)) + { + using (SqlDataReader reader1 = command1.ExecuteReader()) + { + while (reader1.Read()) + { + Assert.Equal(JsonDataString, reader1["data"]); + } + + using (SqlDataReader reader2 = command2.ExecuteReader()) + { + while (reader2.Read()) + { + Assert.Equal(1, reader2["Id"]); + Assert.Equal(JsonDataString, reader2["data"]); + } + } + } + } + } + finally + { + DataTestUtility.DropTable(connection, table1Name); + DataTestUtility.DropTable(connection, table2Name); + } + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsJsonSupported))] + public void TestJsonSPParams() + { + string tableName = DataTestUtility.GenerateObjectName(); + string procName = DataTestUtility.GenerateObjectName(); + string tableInsert = $"INSERT INTO {tableName} VALUES (@id, @jsonData)"; + string tableRead = $"SELECT * FROM {tableName}"; + + using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + { + connection.Open(); + try + { + // Create Table + DataTestUtility.CreateTable(connection, tableName, "(Id int, Data json)"); + + // Create Stored Procedure + string createSP = $@" + @id int, + @jsonData json OUTPUT + AS + BEGIN + SELECT @jsonData = (SELECT Data FROM {tableName} WHERE Id = @id) + END;"; + DataTestUtility.CreateSP(connection, procName, createSP); + + // Insert Data + using (SqlCommand command = new SqlCommand(tableInsert, connection)) + { + command.Parameters.Add(new SqlParameter("@id", SqlDbType.Int) { Value = 1 }); + command.Parameters.Add(new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Value = JsonDataString }); + command.ExecuteNonQuery(); + } + + // Execute Stored Procedure + using (SqlCommand spCommand = new SqlCommand(procName, connection)) + { + spCommand.CommandType = CommandType.StoredProcedure; + spCommand.Parameters.Add(new SqlParameter("@id", SqlDbType.Int) { Direction = ParameterDirection.Input, Value = 1 }); + SqlParameter outputParam = new SqlParameter("@jsonData", SqlDbTypeExtensions.Json) { Direction = ParameterDirection.Output }; + spCommand.Parameters.Add(outputParam); + spCommand.ExecuteNonQuery(); + Assert.Equal(JsonDataString, (string)outputParam.Value); + } + } + finally + { + DataTestUtility.DropTable(connection, tableName); + } + } + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MirroringTest/ConnectionOnMirroringTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MirroringTest/ConnectionOnMirroringTest.cs index 93101d5d38..460d566a6d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MirroringTest/ConnectionOnMirroringTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/MirroringTest/ConnectionOnMirroringTest.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Data; using System.Threading; +using System.Threading.Tasks; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -28,17 +29,14 @@ public static void TestMultipleConnectionToMirroredServer() builder.ConnectTimeout = 0; TestWorker worker = new TestWorker(builder.ConnectionString); - Thread childThread = new Thread(() => worker.TestMultipleConnection()); - childThread.Start(); + Task childTask = Task.Factory.StartNew(() => worker.TestMultipleConnection(), TaskCreationOptions.LongRunning); if (workerCompletedEvent.WaitOne(10000)) { - childThread.Join(); + childTask.Wait(); } else { - // currently Thread.Abort() throws PlatformNotSupportedException in CoreFx. - childThread.Interrupt(); throw new Exception("SqlConnection could not open and close successfully in timely manner. Possibly connection hangs."); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs index f2404c22d3..bf99691907 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -8,7 +8,10 @@ using System.Data; using System.Data.SqlTypes; using System.Threading; +using System.Threading.Tasks; using Xunit; +using System.Globalization; + #if !NETFRAMEWORK using Microsoft.SqlServer.Types; using Microsoft.Data.SqlClient.Server; @@ -442,6 +445,75 @@ public static void TestDateOnlyTVPDataTable_CommandSP() DataTestUtility.DropUserDefinedType(connection, tableTypeName); } } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] + public static void TestDateOnlyTVPSqlDataRecord_CommandSP() + { + string tableTypeName = "[dbo]." + DataTestUtility.GetUniqueNameForSqlServer("UDTTTestDateOnlySqlDataRecordTVP"); + string spName = DataTestUtility.GetUniqueNameForSqlServer("spTestDateOnlySqlDataRecordTVP"); + SqlConnection connection = new(s_connString); + try + { + connection.Open(); + using (SqlCommand cmd = connection.CreateCommand()) + { + cmd.CommandType = CommandType.Text; + cmd.CommandText = $"CREATE TYPE {tableTypeName} AS TABLE ([DateColumn] date NULL, [TimeColumn] time NULL)"; + cmd.ExecuteNonQuery(); + cmd.CommandText = $"CREATE PROCEDURE {spName} (@dates {tableTypeName} READONLY) AS SELECT COUNT(*) FROM @dates"; + cmd.ExecuteNonQuery(); + } + using (SqlCommand cmd = connection.CreateCommand()) + { + cmd.CommandText = spName; + cmd.CommandType = CommandType.StoredProcedure; + + SqlMetaData[] metadata = new SqlMetaData[] + { + new SqlMetaData("DateColumn", SqlDbType.Date), + new SqlMetaData("TimeColumn", SqlDbType.Time) + }; + + SqlDataRecord record1 = new SqlDataRecord(metadata); + record1.SetValues(new DateOnly(2023, 11, 15), new TimeOnly(12, 30, 45)); + + SqlDataRecord record2 = new SqlDataRecord(metadata); + record2.SetValues(new DateOnly(2025, 11, 15), new TimeOnly(13, 31, 46)); + + IList featureInserts = new List + { + record1, + record2, + }; + + cmd.Parameters.Add(new SqlParameter + { + ParameterName = "@dates", + SqlDbType = SqlDbType.Structured, + TypeName = tableTypeName, + Value = featureInserts, + }); + + using var reader = cmd.ExecuteReader(); + + Assert.True(reader.HasRows); + + int count = 0; + while (reader.Read()) + { + Assert.NotNull(reader[0]); + count++; + } + + Assert.Equal(1, count); + } + } + finally + { + DataTestUtility.DropStoredProcedure(connection, spName); + DataTestUtility.DropUserDefinedType(connection, tableTypeName); + } + } #endif #region Scaled Decimal Parameter & TVP Test @@ -471,8 +543,8 @@ public static void SqlDecimalConvertToDecimal_TestInRange(string sqlDecimalValue cmd.Connection = cnn; using SqlDataReader rdr = cmd.ExecuteReader(); Assert.True(rdr.Read(), "SqlDataReader must have a value"); - decimal retrunValue = rdr.GetDecimal(0); - Assert.Equal(expectedDecimalValue, retrunValue.ToString()); + decimal returnValue = rdr.GetDecimal(0); + Assert.Equal(expectedDecimalValue, returnValue.ToString(CultureInfo.InvariantCulture)); } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] @@ -886,30 +958,19 @@ private static void EnableOptimizedParameterBinding_ReturnSucceeds() [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] public static void ClosedConnection_SqlParameterValueTest() { - var threads = new List(); - for (int i = 0; i < 100; i++) + var tasks = new Task[100]; + for (int i = 0; i < tasks.Length; i++) { - var t = new Thread(() => + var t = Task.Factory.StartNew(() => { for (int j = 0; j < 1000; j++) { - try - { - RunParameterTest(); - } - catch (Exception e) - { - Assert.Fail($"Unexpected exception occurred: {e.Message}"); - } + RunParameterTest(); } - }); - t.Start(); - threads.Add(t); - } - for (int i = 0; i < threads.Count; i++) - { - threads[i].Join(); + }, TaskCreationOptions.LongRunning); + tasks[i] = t; } + Task.WaitAll(tasks); } private static void RunParameterTest() @@ -927,7 +988,7 @@ private static void RunParameterTest() cm.Parameters.Add(new SqlParameter("@id2", SqlDbType.UniqueIdentifier) { Direction = ParameterDirection.Output }); try { - System.Threading.Tasks.Task task = cm.ExecuteNonQueryAsync(cancellationToken.Token); + Task task = cm.ExecuteNonQueryAsync(cancellationToken.Token); task.Wait(); } catch (Exception) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/TvpTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/TvpTest.cs index d1fe3bf6ac..8b5b6d0a40 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/TvpTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/TvpTest.cs @@ -1495,27 +1495,6 @@ private void WriteReader(SqlDataReader rdr) while (rdr.NextResult()); } - private void DumpSqlParam(SqlParameter param) - { - Console.WriteLine("Parameter {0}", param.ParameterName); - Console.WriteLine(" IsNullable: {0}", param.IsNullable); - Console.WriteLine(" LocaleId: {0}", param.LocaleId); - Console.WriteLine(" Offset: {0}", param.Offset); - Console.WriteLine(" CompareInfo: {0}", param.CompareInfo); - Console.WriteLine(" DbType: {0}", param.DbType); - Console.WriteLine(" Direction: {0}", param.Direction); - Console.WriteLine(" Precision: {0}", param.Precision); - Console.WriteLine(" Scale: {0}", param.Scale); - Console.WriteLine(" Size: {0}", param.Size); - Console.WriteLine(" SqlDbType: {0}", param.SqlDbType); - Console.WriteLine(" TypeName: {0}", param.TypeName); - //Console.WriteLine(" UdtTypeName: {0}", param.UdtTypeName); - Console.WriteLine(" XmlSchemaCollectionDatabase: {0}", param.XmlSchemaCollectionDatabase); - Console.WriteLine(" XmlSchemaCollectionName: {0}", param.XmlSchemaCollectionName); - Console.WriteLine(" XmlSchemaCollectionSchema: {0}", param.XmlSchemaCollectionOwningSchema); - } - - #endregion } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RandomStressTest/RandomStressTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RandomStressTest/RandomStressTest.cs index 86b5438a1a..79bf05a7f8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RandomStressTest/RandomStressTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RandomStressTest/RandomStressTest.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Text; using System.Threading; +using System.Threading.Tasks; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests @@ -70,8 +71,7 @@ public void TestMain() { for (int tcount = 0; tcount < ThreadCountDefault; tcount++) { - Thread t = new Thread(TestThread); - t.Start(); + _ = Task.Factory.StartNew(TestThread, TaskCreationOptions.LongRunning); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs index cfe01d38ca..61a1b07c5e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs @@ -203,8 +203,10 @@ private SqlConnection CreateConnectionWithInvalidCatalog(string cnnString, SqlRe InitialCatalog = InvalidInitialCatalog }; - SqlConnection cnn = new SqlConnection(builder.ConnectionString); - cnn.RetryLogicProvider = provider; + SqlConnection cnn = new(builder.ConnectionString) + { + RetryLogicProvider = provider + }; cnn.RetryLogicProvider.Retrying += (s, e) => { Assert.Equal(e.RetryCount, e.Exceptions.Count); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SplitPacketTest/SplitPacketTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SplitPacketTest/SplitPacketTest.cs index 1147e704ca..4cfd148d72 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SplitPacketTest/SplitPacketTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SplitPacketTest/SplitPacketTest.cs @@ -11,79 +11,83 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { - [ActiveIssue("5538")] // Only testable on localhost - public class SplitPacketTest + public class SplitPacketTest : IDisposable { - private int Port = -1; - private int SplitPacketSize = 1; - private string BaseConnString; + private int _port = -1; + private int _splitPacketSize = 1; + private string _baseConnString; + private TcpListener _listener; + private CancellationTokenSource _cts = new CancellationTokenSource(); public SplitPacketTest() { - string actualHost; - int actualPort; - SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString); - GetTcpInfoFromDataSource(builder.DataSource, out actualHost, out actualPort); + DataSourceBuilder dataSourceBuilder = new DataSourceBuilder(builder.DataSource); - Task.Factory.StartNew(() => { SetupProxy(actualHost, actualPort); }); + Task.Factory.StartNew(() => { SetupProxy(dataSourceBuilder.ServerName, dataSourceBuilder.Port ?? 1433, _cts.Token); }); - for (int i = 0; i < 10 && Port == -1; i++) + for (int i = 0; i < 10 && _port == -1; i++) { Thread.Sleep(500); } - if (Port == -1) + if (_port == -1) throw new InvalidOperationException("Proxy local port not defined!"); - builder.DataSource = "tcp:127.0.0.1," + Port; - BaseConnString = builder.ConnectionString; + builder.DataSource = "tcp:127.0.0.1," + _port; + _baseConnString = builder.ConnectionString; } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsTCPConnStringSetup), nameof(DataTestUtility.IsLocalHost), nameof(DataTestUtility.IsNotNamedInstance))] public void OneByteSplitTest() { - SplitPacketSize = 1; + _splitPacketSize = 1; OpenConnection(); + Assert.True(true); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsTCPConnStringSetup), nameof(DataTestUtility.IsLocalHost), nameof(DataTestUtility.IsNotNamedInstance))] public void AlmostFullHeaderTest() { - SplitPacketSize = 7; + _splitPacketSize = 7; OpenConnection(); + Assert.True(true); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsTCPConnStringSetup), nameof(DataTestUtility.IsLocalHost), nameof(DataTestUtility.IsNotNamedInstance))] public void FullHeaderTest() { - SplitPacketSize = 8; + _splitPacketSize = 8; OpenConnection(); + Assert.True(true); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsTCPConnStringSetup), nameof(DataTestUtility.IsLocalHost), nameof(DataTestUtility.IsNotNamedInstance))] public void HeaderPlusOneTest() { - SplitPacketSize = 9; + _splitPacketSize = 9; OpenConnection(); + Assert.True(true); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsTCPConnStringSetup), nameof(DataTestUtility.IsLocalHost), nameof(DataTestUtility.IsNotNamedInstance))] public void MARSSplitTest() { - SplitPacketSize = 1; + _splitPacketSize = 1; OpenMarsConnection("select * from Orders"); + Assert.True(true); } - [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsTCPConnStringSetup), nameof(DataTestUtility.IsLocalHost), nameof(DataTestUtility.IsNotNamedInstance))] public void MARSReplicateTest() { - SplitPacketSize = 1; + _splitPacketSize = 1; OpenMarsConnection("select REPLICATE('A', 10000)"); + Assert.True(true); } private void OpenMarsConnection(string cmdText) { - using (SqlConnection conn = new SqlConnection((new SqlConnectionStringBuilder(BaseConnString) { MultipleActiveResultSets = true }).ConnectionString)) + using (SqlConnection conn = new SqlConnection((new SqlConnectionStringBuilder(_baseConnString) { MultipleActiveResultSets = true }).ConnectionString)) { conn.Open(); using (SqlCommand cmd1 = new SqlCommand(cmdText, conn)) @@ -102,7 +106,7 @@ private void OpenMarsConnection(string cmdText) private void OpenConnection() { - using (SqlConnection conn = new SqlConnection(BaseConnString)) + using (SqlConnection conn = new SqlConnection(_baseConnString)) { conn.Open(); using (SqlCommand cmd = new SqlCommand("select * from Orders", conn)) @@ -114,23 +118,23 @@ private void OpenConnection() } } - private void SetupProxy(string actualHost, int actualPort) + private void SetupProxy(string actualHost, int actualPort, CancellationToken cancellationToken) { - TcpListener listener = new TcpListener(IPAddress.Loopback, 0); - listener.Start(); - Port = ((IPEndPoint)listener.LocalEndpoint).Port; - var client = listener.AcceptTcpClientAsync().GetAwaiter().GetResult(); + _listener = new TcpListener(IPAddress.Loopback, 0); + _listener.Start(); + _port = ((IPEndPoint)_listener.LocalEndpoint).Port; + var client = _listener.AcceptTcpClientAsync().GetAwaiter().GetResult(); var sqlClient = new TcpClient(); - sqlClient.ConnectAsync(actualHost, actualPort).Wait(); + sqlClient.ConnectAsync(actualHost, actualPort).Wait(cancellationToken); - Task.Factory.StartNew(() => { ForwardToSql(client, sqlClient); }); - Task.Factory.StartNew(() => { ForwardToClient(client, sqlClient); }); + Task.Factory.StartNew(() => { ForwardToSql(client, sqlClient, cancellationToken); }, cancellationToken); + Task.Factory.StartNew(() => { ForwardToClient(client, sqlClient, cancellationToken); }, cancellationToken); } - private void ForwardToSql(TcpClient ourClient, TcpClient sqlClient) + private void ForwardToSql(TcpClient ourClient, TcpClient sqlClient, CancellationToken cancellationToken) { - while (true) + while (!cancellationToken.IsCancellationRequested) { byte[] buffer = new byte[1024]; int bytesRead = ourClient.GetStream().Read(buffer, 0, buffer.Length); @@ -139,11 +143,11 @@ private void ForwardToSql(TcpClient ourClient, TcpClient sqlClient) } } - private void ForwardToClient(TcpClient ourClient, TcpClient sqlClient) + private void ForwardToClient(TcpClient ourClient, TcpClient sqlClient, CancellationToken cancellationToken) { - while (true) + while (!cancellationToken.IsCancellationRequested) { - byte[] buffer = new byte[SplitPacketSize]; + byte[] buffer = new byte[_splitPacketSize]; int bytesRead = sqlClient.GetStream().Read(buffer, 0, buffer.Length); ourClient.GetStream().Write(buffer, 0, bytesRead); @@ -155,22 +159,24 @@ private void ForwardToClient(TcpClient ourClient, TcpClient sqlClient) } } - private static void GetTcpInfoFromDataSource(string dataSource, out string hostName, out int port) + public void Dispose() { - string[] dataSourceParts = dataSource.Split(','); - if (dataSourceParts.Length == 1) - { - hostName = dataSourceParts[0].Replace("tcp:", ""); - port = 1433; - } - else if (dataSourceParts.Length == 2) - { - hostName = dataSourceParts[0].Replace("tcp:", ""); - port = int.Parse(dataSourceParts[1]); - } - else + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) { - throw new InvalidOperationException("TCP Connection String not in correct format!"); + _cts.Cancel(); + _cts.Dispose(); + _listener?.Server.Dispose(); +#if NETFRAMEWORK + _listener?.Stop(); +#else + _listener?.Dispose(); +#endif } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumn.cs index 481a99a83c..d8fe8f66da 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumn.cs @@ -35,7 +35,9 @@ public static void Test(string srcConstr, string dstConstr, string dstTable) ColumnMappings.Add("LastName", "col2"); // this column does not exist ColumnMappings.Add("FirstName", "col3"); - string errorMsg = SystemDataResourceManager.Instance.SQL_BulkLoadNonMatchingColumnMapping; + string errorMsg = SystemDataResourceManager.Instance.SQL_BulkLoadNonMatchingColumnName; + errorMsg = string.Format(errorMsg, "col2"); + DataTestUtility.AssertThrowsWrapper(() => bulkcopy.WriteToServer(reader), exceptionMessage: errorMsg); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumns.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumns.cs new file mode 100644 index 0000000000..2696770f69 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/MissingTargetColumns.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Data.Common; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests +{ + public class MissingTargetColumns + { + public static void Test(string srcConstr, string dstConstr, string dstTable) + { + using (SqlConnection dstConn = new SqlConnection(dstConstr)) + using (SqlCommand dstCmd = dstConn.CreateCommand()) + { + dstConn.Open(); + + try + { + Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 nvarchar(10))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) + { + srcConn.Open(); + + using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) + { + bulkcopy.DestinationTableName = dstTable; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + + ColumnMappings.Add("EmployeeID", "col1"); + ColumnMappings.Add("LastName", "col3"); // this column does not exist + ColumnMappings.Add("FirstName", "col4"); // this column does not exist + + string errorMsg = SystemDataResourceManager.Instance.SQL_BulkLoadNonMatchingColumnName; + errorMsg = string.Format(errorMsg, "col3,col4"); + + DataTestUtility.AssertThrowsWrapper(() => bulkcopy.WriteToServer(reader), exceptionMessage: errorMsg); + } + } + } + finally + { + Helpers.TryExecute(dstCmd, "drop table " + dstTable); + } + } + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs index f7cb3e7490..4672adb242 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/SqlBulkCopyTest.cs @@ -104,6 +104,12 @@ public void MissingTargetColumnTest() MissingTargetColumn.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_MissingTargetColumn")); } + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] + public void MissingTargetColumnsTest() + { + MissingTargetColumns.Test(_connStr, _connStr, AddGuid("SqlBulkCopyTest_MissingTargetColumns")); + } + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))] public void Bug85007Test() { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs new file mode 100644 index 0000000000..dc3779b4dc --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/TestBulkCopyWithUTF8.cs @@ -0,0 +1,200 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Data; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests +{ + /// + /// Validates SqlBulkCopy functionality when working with UTF-8 encoded data. + /// Ensures that data copied from a UTF-8 source table to a destination table retains its encoding and content integrity. + /// + public sealed class TestBulkCopyWithUtf8 : IDisposable + { + private static string s_sourceTable = DataTestUtility.GetUniqueName("SourceTableForUTF8Data"); + private static string s_destinationTable = DataTestUtility.GetUniqueName("DestinationTableForUTF8Data"); + private static string s_testValue = "test"; + private static byte[] s_testValueInUtf8Bytes = new byte[] { 0x74, 0x65, 0x73, 0x74 }; + private static readonly string s_insertQuery = $"INSERT INTO {s_sourceTable} VALUES('{s_testValue}')"; + + /// + /// Constructor: Initializes and populates source and destination tables required for the tests. + /// + public TestBulkCopyWithUtf8() + { + using SqlConnection sourceConnection = new SqlConnection(GetConnectionString(true)); + sourceConnection.Open(); + SetupTables(sourceConnection, s_sourceTable, s_destinationTable, s_insertQuery); + } + + /// + /// Cleanup method to drop tables after test completion. + /// + public void Dispose() + { + using SqlConnection connection = new SqlConnection(GetConnectionString(true)); + connection.Open(); + DataTestUtility.DropTable(connection, s_sourceTable); + DataTestUtility.DropTable(connection, s_destinationTable); + connection.Close(); + } + + /// + /// Builds a connection string with or without Multiple Active Result Sets (MARS) property. + /// + private string GetConnectionString(bool enableMars) + { + return new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) + { + MultipleActiveResultSets = enableMars + }.ConnectionString; + } + + /// + /// Creates source and destination tables with a varchar(max) column with a collation setting + /// that stores the data in UTF8 encoding and inserts the data in the source table. + /// + private void SetupTables(SqlConnection connection, string sourceTable, string destinationTable, string insertQuery) + { + string columnDefinition = "(str_col varchar(max) COLLATE Latin1_General_100_CS_AS_KS_WS_SC_UTF8)"; + DataTestUtility.CreateTable(connection, sourceTable, columnDefinition); + DataTestUtility.CreateTable(connection, destinationTable, columnDefinition); + using SqlCommand insertCommand = connection.CreateCommand(); + insertCommand.CommandText = insertQuery; + Helpers.TryExecute(insertCommand, insertQuery); + } + + /// + /// Synchronous test case: Validates that data copied using SqlBulkCopy matches UTF-8 byte sequence for test value. + /// Tested with MARS enabled and disabled, and with streaming enabled and disabled. + /// + [ConditionalTheory(typeof(DataTestUtility), + nameof(DataTestUtility.AreConnStringsSetup), + nameof(DataTestUtility.IsNotAzureServer), + nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(false, false)] + public void BulkCopy_Utf8Data_ShouldMatchSource(bool isMarsEnabled, bool enableStreaming) + { + // Setup connections for source and destination tables + string connectionString = GetConnectionString(isMarsEnabled); + using SqlConnection sourceConnection = new SqlConnection(connectionString); + sourceConnection.Open(); + using SqlConnection destinationConnection = new SqlConnection(connectionString); + destinationConnection.Open(); + + // Read data from source table + using SqlCommand sourceDataCommand = new SqlCommand($"SELECT str_col FROM {s_sourceTable}", sourceConnection); + using SqlDataReader reader = sourceDataCommand.ExecuteReader(CommandBehavior.SequentialAccess); + + // Verify that the destination table is empty before bulk copy + using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {s_destinationTable}", destinationConnection); + Assert.Equal(0, Convert.ToInt16(countCommand.ExecuteScalar())); + + // Initialize bulk copy configuration + using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) + { + EnableStreaming = enableStreaming, + DestinationTableName = s_destinationTable + }; + + try + { + // Perform bulk copy from source to destination table + bulkCopy.WriteToServer(reader); + } + catch (Exception ex) + { + // If bulk copy fails, fail the test with the exception message + Assert.Fail($"Bulk copy failed: {ex.Message}"); + } + + // Verify that the 1 row from the source table has been copied into our destination table. + Assert.Equal(1, Convert.ToInt16(countCommand.ExecuteScalar())); + + // Read the data from destination table as varbinary to verify the UTF-8 byte sequence + using SqlCommand verifyCommand = new SqlCommand($"SELECT cast(str_col as varbinary) FROM {s_destinationTable}", destinationConnection); + using SqlDataReader verifyReader = verifyCommand.ExecuteReader(CommandBehavior.SequentialAccess); + + // Verify that we have data in the destination table + Assert.True(verifyReader.Read(), "No data found in destination table after bulk copy."); + + // Read the value of the column as SqlBinary. + byte[] actualBytes = verifyReader.GetSqlBinary(0).Value; + + // Verify that the byte array matches the expected UTF-8 byte sequence + Assert.Equal(s_testValueInUtf8Bytes.Length, actualBytes.Length); + Assert.Equal(s_testValueInUtf8Bytes, actualBytes); + } + + /// + /// Asynchronous version of the testcase BulkCopy_Utf8Data_ShouldMatchSource + /// + [ConditionalTheory(typeof(DataTestUtility), + nameof(DataTestUtility.AreConnStringsSetup), + nameof(DataTestUtility.IsNotAzureServer), + nameof(DataTestUtility.IsNotAzureSynapse))] + [InlineData(true, true)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(false, false)] + public async Task BulkCopy_Utf8Data_ShouldMatchSource_Async(bool isMarsEnabled, bool enableStreaming) + { + // Setup connections for source and destination tables + string connectionString = GetConnectionString(isMarsEnabled); + using SqlConnection sourceConnection = new SqlConnection(connectionString); + await sourceConnection.OpenAsync(); + using SqlConnection destinationConnection = new SqlConnection(connectionString); + await destinationConnection.OpenAsync(); + + // Read data from source table + using SqlCommand sourceDataCommand = new SqlCommand($"SELECT str_col FROM {s_sourceTable}", sourceConnection); + using SqlDataReader reader = await sourceDataCommand.ExecuteReaderAsync(CommandBehavior.SequentialAccess); + + // Verify that the destination table is empty before bulk copy + using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {s_destinationTable}", destinationConnection); + Assert.Equal(0, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); + + // Initialize bulk copy configuration + using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) + { + EnableStreaming = enableStreaming, + DestinationTableName = s_destinationTable + }; + + try + { + // Perform bulk copy from source to destination table + await bulkCopy.WriteToServerAsync(reader); + } + catch (Exception ex) + { + // If bulk copy fails, fail the test with the exception message + Assert.Fail($"Bulk copy failed: {ex.Message}"); + } + + // Verify that the 1 row from the source table has been copied into our destination table. + Assert.Equal(1, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); + + // Read the data from destination table as varbinary to verify the UTF-8 byte sequence + using SqlCommand verifyCommand = new SqlCommand($"SELECT cast(str_col as varbinary) FROM {s_destinationTable}", destinationConnection); + using SqlDataReader verifyReader = await verifyCommand.ExecuteReaderAsync(CommandBehavior.SequentialAccess); + + // Verify that we have data in the destination table + Assert.True(await verifyReader.ReadAsync(), "No data found in destination table after bulk copy."); + + // Read the value of the column as SqlBinary. + byte[] actualBytes = verifyReader.GetSqlBinary(0).Value; + + // Verify that the byte array matches the expected UTF-8 byte sequence + Assert.Equal(s_testValueInUtf8Bytes.Length, actualBytes.Length); + Assert.Equal(s_testValueInUtf8Bytes, actualBytes); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlDSEnumeratorTest/SqlDataSourceEnumeratorTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlDSEnumeratorTest/SqlDataSourceEnumeratorTest.cs index 194dc6996e..a81731c0a1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlDSEnumeratorTest/SqlDataSourceEnumeratorTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlDSEnumeratorTest/SqlDataSourceEnumeratorTest.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.ComponentModel; using System.Linq; using System.ServiceProcess; using Microsoft.Data.Sql; @@ -43,6 +44,28 @@ public void SqlDataSourceEnumerator_ManagedSNI() Assert.Throws(() => GetDSEnumerator().GetDataSources()); } + // This test validates behavior of SqlDataSourceConverter used to present instance names in PropertyGrid + // with the SqlConnectionStringBuilder object presented in the control underneath. + [ConditionalFact(nameof(IsEnvironmentAvailable))] + [PlatformSpecific(TestPlatforms.Windows)] + public void TestDataSourceConverterGetStandardValues() + { + PropertyDescriptorCollection csbDescriptor = TypeDescriptor.GetProperties(typeof(SqlConnectionStringBuilder)); + PropertyDescriptor dataSourceDescriptor = csbDescriptor.Find(nameof(SqlConnectionStringBuilder.DataSource), false); + TypeConverter converter = dataSourceDescriptor?.Converter; + + Assert.NotNull(dataSourceDescriptor); + Assert.NotNull(converter); + + Assert.True(converter.GetStandardValuesSupported()); + Assert.False(converter.GetStandardValuesExclusive()); + + System.Collections.ICollection dataSources = converter.GetStandardValues(); + int count = dataSources.Count; + + Assert.InRange(count, 0, 65536); + } + private SqlDataSourceEnumerator GetDSEnumerator() { // SQL Server Browser runs as a Windows service. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlSchemaInfoTest/SqlSchemaInfoTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlSchemaInfoTest/SqlSchemaInfoTest.cs index 83c92b7236..4d2e964a5a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlSchemaInfoTest/SqlSchemaInfoTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlSchemaInfoTest/SqlSchemaInfoTest.cs @@ -99,13 +99,17 @@ public static void TestInitialCatalogStandardValues() SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(connection.ConnectionString); PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(builder); PropertyDescriptor descriptor = properties["InitialCatalog"]; + DescriptorContext descriptorContext = new DescriptorContext(descriptor, builder); DataTestUtility.AssertEqualsWithDescription( "SqlInitialCatalogConverter", descriptor.Converter.GetType().Name, "Unexpected TypeConverter type."); + Assert.True(descriptor.Converter.GetStandardValuesSupported(descriptorContext)); + Assert.False(descriptor.Converter.GetStandardValuesExclusive()); + // GetStandardValues of this converter calls GetSchema("DATABASES") - var dbNames = descriptor.Converter.GetStandardValues(new DescriptorContext(descriptor, builder)); + var dbNames = descriptor.Converter.GetStandardValues(descriptorContext); HashSet searchSet = new HashSet(StringComparer.InvariantCultureIgnoreCase); foreach (string name in dbNames) { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/DistributedTransactionTest.Windows.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/DistributedTransactionTest.Windows.cs new file mode 100644 index 0000000000..049ed28092 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/DistributedTransactionTest.Windows.cs @@ -0,0 +1,177 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Data; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using System.Transactions; +using Microsoft.Data.SqlClient.TestUtilities; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests +{ + + [PlatformSpecific(TestPlatforms.Windows)] + public class DistributedTransactionTestWindows + { + +#if NET + private static bool s_DelegatedTransactionCondition => DataTestUtility.AreConnStringsSetup() && DataTestUtility.IsNotAzureServer() && DataTestUtility.IsNotX86Architecture; + + [ConditionalFact(nameof(s_DelegatedTransactionCondition), Timeout = 10000)] + public async Task Delegated_transaction_deadlock_in_SinglePhaseCommit() + { + TransactionManager.ImplicitDistributedTransactions = true; + using var transaction = new CommittableTransaction(); + + // Uncommenting the following makes the deadlock go away as a workaround. If the transaction is promoted before + // the first SqlClient enlistment, it never goes into the delegated state. + // _ = TransactionInterop.GetTransmitterPropagationToken(transaction); + await using var conn = new SqlConnection(DataTestUtility.TCPConnectionString); + await conn.OpenAsync(); + conn.EnlistTransaction(transaction); + + // Enlisting the transaction in second connection causes the transaction to be promoted. + // After this, the transaction state will be "delegated" (delegated to SQL Server), and the commit below will + // trigger a call to SqlDelegatedTransaction.SinglePhaseCommit. + await using var conn2 = new SqlConnection(DataTestUtility.TCPConnectionString); + await conn2.OpenAsync(); + conn2.EnlistTransaction(transaction); + + // Possible deadlock + transaction.Commit(); + } +#endif + + private static bool s_EnlistedTransactionPreservedWhilePooledCondition => DataTestUtility.AreConnStringsSetup() && DataTestUtility.IsNotX86Architecture; + + [ConditionalFact(nameof(s_EnlistedTransactionPreservedWhilePooledCondition), Timeout = 10000)] + public async Task Test_EnlistedTransactionPreservedWhilePooled() + { +#if NET + TransactionManager.ImplicitDistributedTransactions = true; +#endif + await RunTestSet(EnlistedTransactionPreservedWhilePooled); + } + + private async Task EnlistedTransactionPreservedWhilePooled() + { + Exception commandException = null; + Exception transactionException = null; + + try + { + using (TransactionScope txScope = new TransactionScope(TransactionScopeOption.Required, TimeSpan.MaxValue)) + { + // Leave first connection open so that the transaction is promoted + SqlConnection rootConnection = new SqlConnection(ConnectionString); + await rootConnection.OpenAsync(); + using (SqlCommand command = rootConnection.CreateCommand()) + { + command.CommandText = $"INSERT INTO {TestTableName} VALUES ({InputCol1}, '{InputCol2}')"; + command.ExecuteNonQuery(); + } + + // Closing and reopening cycles the connection through the pool. + // We want to verify that the transaction state is preserved through this cycle. + SqlConnection enlistedConnection = new SqlConnection(ConnectionString); + enlistedConnection.Open(); + enlistedConnection.Close(); + enlistedConnection.Open(); + + // Forcibly kill the root connection to mimic gateway's behavior when using the proxy connection policy + // https://techcommunity.microsoft.com/blog/azuredbsupport/azure-sql-database-idle-sessions-are-killed-after-about-30-minutes-when-proxy-co/3268601 + // Can also represent a general server-side, process failure + KillProcess(rootConnection.ServerProcessId); + + + using (SqlCommand command = enlistedConnection.CreateCommand()) + { + command.CommandText = $"INSERT INTO {TestTableName} VALUES ({InputCol1}, '{InputCol2}')"; + try + { + command.ExecuteNonQuery(); + } + catch (Exception ex) + { + commandException = ex; + } + } + + txScope.Complete(); + } + } + catch (Exception ex) + { + transactionException = ex; + } + + // Even if an application swallows the command exception, completing the transaction should indicate that it failed. + Type[] expectedTransactionExceptions = new[] { typeof(TransactionAbortedException), typeof(TransactionInDoubtException) }; + Assert.Contains(transactionException.GetType(), expectedTransactionExceptions); + + Type[] expectedCommandExceptions = new[] { typeof(SqlException), typeof(InvalidOperationException) }; + Assert.Contains(commandException.GetType(), expectedCommandExceptions); + + if (commandException is SqlException exception) + { + // See https://learn.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors-8000-to-8999?view=sql-server-ver16 + // The distributed transaction failed + // See https://learn.microsoft.com/en-us/sql/relational-databases/errors-events/database-engine-events-and-errors-3000-to-3999?view=sql-server-ver16 + // Error 3971 corresponds to "The server failed to resume the transaction." + var expectedExceptionCodes = new[] { 3971, 8525 }; + Assert.Contains(exception.Number, expectedExceptionCodes); + } + + // Verify that nothing made it into the database + DataTable result = DataTestUtility.RunQuery(ConnectionString, $"select col2 from {TestTableName} where col1 = {InputCol1}"); + Assert.Equal(0, result.Rows.Count); + } + + private void KillProcess(int serverProcessId) + { + using (TransactionScope txScope = new TransactionScope(TransactionScopeOption.Suppress)) + { + using (SqlConnection connection = new SqlConnection(ConnectionString)) + { + connection.Open(); + using (SqlCommand command = connection.CreateCommand()) + { + command.CommandText = $"KILL {serverProcessId}"; + command.ExecuteNonQuery(); + } + } + txScope.Complete(); + } + } + + private static string TestTableName; + private static string ConnectionString; + private const int InputCol1 = 1; + private const string InputCol2 = "One"; + + private static async Task RunTestSet(Func TestCase) + { + SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString); + + builder.Pooling = true; + builder.MaxPoolSize = 5; + builder.Enlist = true; + ConnectionString = builder.ConnectionString; + + TestTableName = DataTestUtility.GenerateObjectName(); + DataTestUtility.RunNonQuery(ConnectionString, $"create table {TestTableName} (col1 int, col2 text)"); + try + { + await TestCase(); + } + finally + { + DataTestUtility.RunNonQuery(ConnectionString, $"drop table {TestTableName}"); + } + } + } +} + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/DistributedTransactionTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/DistributedTransactionTest.cs deleted file mode 100644 index 658104e193..0000000000 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/DistributedTransactionTest.cs +++ /dev/null @@ -1,46 +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.Runtime.InteropServices; -using System.Threading.Tasks; -using System.Transactions; -using Xunit; - -#if NET8_0_OR_GREATER - -namespace Microsoft.Data.SqlClient.ManualTesting.Tests -{ - [PlatformSpecific(TestPlatforms.Windows)] - public class DistributedTransactionTest - { - private static bool s_DelegatedTransactionCondition => DataTestUtility.AreConnStringsSetup() && DataTestUtility.IsNotAzureServer() && DataTestUtility.IsNotX86Architecture; - - [ConditionalFact(nameof(s_DelegatedTransactionCondition), Timeout = 10000)] - public async Task Delegated_transaction_deadlock_in_SinglePhaseCommit() - { - TransactionManager.ImplicitDistributedTransactions = true; - using var transaction = new CommittableTransaction(); - - // Uncommenting the following makes the deadlock go away as a workaround. If the transaction is promoted before - // the first SqlClient enlistment, it never goes into the delegated state. - // _ = TransactionInterop.GetTransmitterPropagationToken(transaction); - await using var conn = new SqlConnection(DataTestUtility.TCPConnectionString); - await conn.OpenAsync(); - conn.EnlistTransaction(transaction); - - // Enlisting the transaction in second connection causes the transaction to be promoted. - // After this, the transaction state will be "delegated" (delegated to SQL Server), and the commit below will - // trigger a call to SqlDelegatedTransaction.SinglePhaseCommit. - await using var conn2 = new SqlConnection(DataTestUtility.TCPConnectionString); - await conn2.OpenAsync(); - conn2.EnlistTransaction(transaction); - - // Possible deadlock - transaction.Commit(); - } - } -} - -#endif diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/SqlServerTypesTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/SqlServerTypesTest.cs index bdbd805e73..0fd8f22daf 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/SqlServerTypesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/SqlServerTypesTest.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data; -using System.Data.Common; using System.Data.SqlTypes; using System.Globalization; using System.IO; @@ -252,7 +251,6 @@ private static void TestUdtSqlDataReaderGetStream(CommandBehavior behavior) } // Synapse: Parse error at line: 1, column: 41: Incorrect syntax near 'hierarchyid'. - [ActiveIssue("25421", TargetFrameworkMonikers.NetFramework)] [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] public static void TestUdtSchemaMetadata() { @@ -263,9 +261,9 @@ public static void TestUdtSchemaMetadata() command.CommandText = "select hierarchyid::Parse('/1/1/3/') as col0, geometry::Parse('LINESTRING (100 100, 20 180, 180 180)') as col1, geography::Parse('LINESTRING(-122.360 47.656, -122.343 47.656)') as col2"; using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.SchemaOnly)) { - ReadOnlyCollection columns = reader.GetColumnSchema(); + ReadOnlyCollection columns = reader.GetColumnSchema(); - DbColumn column = null; + System.Data.Common.DbColumn column = null; // Validate Microsoft.SqlServer.Types.SqlHierarchyId, Microsoft.SqlServer.Types, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91 column = columns[0]; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Address/Address.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Address/Address.csproj index 941b08588c..d1487d9012 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Address/Address.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Address/Address.csproj @@ -12,6 +12,6 @@ - + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Circle.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Circle.csproj index 93137d1883..8e1719e8d3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Circle.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Circle/Circle.csproj @@ -3,7 +3,7 @@ Circle Circle {6C88F00F-9597-43AD-9E5F-9B344DA3B16F} - netfx + netfx netcoreapp $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName) $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName) @@ -12,6 +12,6 @@ - + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Shapes.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Shapes.csproj index 596f7ffe74..ccd0d626d9 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Shapes.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Shapes/Shapes.csproj @@ -5,13 +5,13 @@ {B73A7063-37C3-415D-AD53-BB3DA20ABD6E} netfx netcoreapp - $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName) + $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName) $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName) - + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Utf8String/Utf8String.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Utf8String/Utf8String.csproj index ec1229e0e7..1a7717e844 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Utf8String/Utf8String.csproj +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/UdtTest/UDTs/Utf8String/Utf8String.csproj @@ -5,13 +5,13 @@ {E0A6BB21-574B-43D9-890D-6E1144F2EE9E} netfx netcoreapp - $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName) + $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName) $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName) - + diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs new file mode 100644 index 0000000000..3140d4c3ac --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorFloat32Tests.cs @@ -0,0 +1,590 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlTypes; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.Data.SqlTypes; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest +{ + public static class VectorFloat32TestData + { + public const int VectorHeaderSize = 8; + public static float[] testData = new float[] { 1.1f, 2.2f, 3.3f }; + public static int vectorColumnLength = testData.Length; + // Incorrect size for SqlParameter.Size + public static int IncorrectParamSize = 3234; + public static IEnumerable GetVectorFloat32TestData() + { + // Pattern 1-4 with SqlVector(values: testData) + yield return new object[] { 1, new SqlVector(testData), testData, vectorColumnLength }; + yield return new object[] { 2, new SqlVector(testData), testData, vectorColumnLength }; + yield return new object[] { 3, new SqlVector(testData), testData, vectorColumnLength }; + yield return new object[] { 4, new SqlVector(testData), testData, vectorColumnLength }; + + // Pattern 1-4 with SqlVector(n) + yield return new object[] { 1, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength }; + yield return new object[] { 2, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength }; + yield return new object[] { 3, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength }; + yield return new object[] { 4, SqlVector.CreateNull(vectorColumnLength), Array.Empty(), vectorColumnLength }; + + // Pattern 1-4 with DBNull + yield return new object[] { 1, DBNull.Value, Array.Empty(), vectorColumnLength }; + yield return new object[] { 2, DBNull.Value, Array.Empty(), vectorColumnLength }; + yield return new object[] { 3, DBNull.Value, Array.Empty(), vectorColumnLength }; + yield return new object[] { 4, DBNull.Value, Array.Empty(), vectorColumnLength }; + + // Pattern 1-4 with SqlVector.Null + yield return new object[] { 1, SqlVector.Null, Array.Empty(), vectorColumnLength }; + + // Following scenario is not supported in SqlClient. + // This can only be fixed with a behavior change that SqlParameter.Value is internally set to DBNull.Value if it is set to null. + //yield return new object[] { 2, SqlVector.Null, Array.Empty(), vectorColumnLength }; + + yield return new object[] { 3, SqlVector.Null, Array.Empty(), vectorColumnLength }; + yield return new object[] { 4, SqlVector.Null, Array.Empty(), vectorColumnLength }; + } + } + + public sealed class NativeVectorFloat32Tests : IDisposable + { + private readonly ITestOutputHelper _output; + private static readonly string s_connectionString = ManualTesting.Tests.DataTestUtility.TCPConnectionString; + private static readonly string s_tableName = DataTestUtility.GetUniqueName("VectorTestTable"); + private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetUniqueName("VectorBulkCopyTestTable"); + private static readonly string s_bulkCopySrcTableDef = $@"(Id INT PRIMARY KEY IDENTITY, VectorData vector(3) NULL)"; + private static readonly string s_tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, VectorData vector(3) NULL)"; + private static readonly string s_selectCmdString = $"SELECT VectorData FROM {s_tableName} ORDER BY Id DESC"; + private static readonly string s_insertCmdString = $"INSERT INTO {s_tableName} (VectorData) VALUES (@VectorData)"; + private static readonly string s_vectorParamName = $"@VectorData"; + private static readonly string s_outputVectorParamName = $"@OutputVectorData"; + private static readonly string s_storedProcName = DataTestUtility.GetUniqueName("VectorsAsVarcharSp"); + private static readonly string s_storedProcBody = $@" + {s_vectorParamName} vector(3), -- Input: Serialized float[] as JSON string + {s_outputVectorParamName} vector(3) OUTPUT -- Output: Echoed back from latest inserted row + AS + BEGIN + SET NOCOUNT ON; + + -- Insert into vector table + INSERT INTO {s_tableName} (VectorData) + VALUES ({s_vectorParamName}); + + -- Retrieve latest entry (assumes auto-incrementing ID) + SELECT TOP 1 {s_outputVectorParamName} = VectorData + FROM {s_tableName} + ORDER BY Id DESC; + END;"; + + public NativeVectorFloat32Tests(ITestOutputHelper output) + { + _output = output; + using var connection = new SqlConnection(s_connectionString); + connection.Open(); + DataTestUtility.CreateTable(connection, s_tableName, s_tableDefinition); + DataTestUtility.CreateTable(connection, s_bulkCopySrcTableName, s_bulkCopySrcTableDef); + DataTestUtility.CreateSP(connection, s_storedProcName, s_storedProcBody); + } + + public void Dispose() + { + using var connection = new SqlConnection(s_connectionString); + connection.Open(); + DataTestUtility.DropTable(connection, s_tableName); + DataTestUtility.DropTable(connection, s_bulkCopySrcTableName); + DataTestUtility.DropStoredProcedure(connection, s_storedProcName); + } + + private void ValidateSqlVectorFloat32Object(bool isNull, SqlVector sqlVectorFloat32, float[] expectedData, int expectedLength) + { + Assert.Equal(expectedData, sqlVectorFloat32.Memory.ToArray()); + Assert.Equal(expectedLength, sqlVectorFloat32.Length); + if (!isNull) + { + Assert.False(sqlVectorFloat32.IsNull, "IsNull set to true for a non-null value"); + } + else + { + Assert.True(sqlVectorFloat32.IsNull, "IsNull set to false for a null value"); + } + } + + private void ValidateInsertedData(SqlConnection connection, float[] expectedData, int expectedLength) + { + using var selectCmd = new SqlCommand(s_selectCmdString, connection); + using var reader = selectCmd.ExecuteReader(); + Assert.True(reader.Read(), "No data found in the table."); + + //For both null and non-null cases, validate the SqlVector object + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), reader.GetFieldValue>(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); + + if (!reader.IsDBNull(0)) + { + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader[0], expectedData, expectedLength); + ValidateSqlVectorFloat32Object(reader.IsDBNull(0), (SqlVector)reader["VectorData"], expectedData, expectedLength); + Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetString(0))); + Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetSqlString(0).Value)); + Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetFieldValue(0))); + } + else + { + Assert.Equal(DBNull.Value, reader.GetValue(0)); + Assert.Equal(DBNull.Value, reader[0]); + Assert.Equal(DBNull.Value, reader["VectorData"]); + Assert.Throws(() => reader.GetString(0)); + Assert.Throws(() => reader.GetSqlString(0).Value); + Assert.Throws(() => reader.GetFieldValue(0)); + } + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))] + [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] + public void TestSqlVectorFloat32ParameterInsertionAndReads( + int pattern, + object value, + float[] expectedValues, + int expectedLength) + { + using var conn = new SqlConnection(s_connectionString); + conn.Open(); + + using var insertCmd = new SqlCommand(s_insertCmdString, conn); + + SqlParameter param = pattern switch + { + 1 => new SqlParameter + { + ParameterName = s_vectorParamName, + SqlDbType = SqlDbTypeExtensions.Vector, + Value = value + }, + 2 => new SqlParameter(s_vectorParamName, value), + 3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value }, + // Even if size is specified, the actual size is determined by the value passed and specified size is ignored. + 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, + _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") + }; + + insertCmd.Parameters.Add(param); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + + ValidateInsertedData(conn, expectedValues, expectedLength); + } + + private async Task ValidateInsertedDataAsync(SqlConnection connection, float[] expectedData, int expectedLength) + { + using var selectCmd = new SqlCommand(s_selectCmdString, connection); + using var reader = await selectCmd.ExecuteReaderAsync(); + Assert.True(await reader.ReadAsync(), "No data found in the table."); + + //For both null and non-null cases, validate the SqlVector object + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlVector(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), await reader.GetFieldValueAsync>(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetSqlValue(0), expectedData, expectedLength); + + if (!await reader.IsDBNullAsync(0)) + { + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader.GetValue(0), expectedData, expectedLength); + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader[0], expectedData, expectedLength); + ValidateSqlVectorFloat32Object(await reader.IsDBNullAsync(0), (SqlVector)reader["VectorData"], expectedData, expectedLength); + Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetString(0))); + Assert.Equal(expectedData, JsonSerializer.Deserialize(reader.GetSqlString(0).Value)); + Assert.Equal(expectedData, JsonSerializer.Deserialize(await reader.GetFieldValueAsync(0))); + } + else + { + Assert.Equal(DBNull.Value, reader.GetValue(0)); + Assert.Equal(DBNull.Value, reader[0]); + Assert.Equal(DBNull.Value, reader["VectorData"]); + Assert.Throws(() => reader.GetString(0)); + Assert.Throws(() => reader.GetSqlString(0).Value); + await Assert.ThrowsAsync(async () => await reader.GetFieldValueAsync(0)); + } + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))] + [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] + public async Task TestSqlVectorFloat32ParameterInsertionAndReadsAsync( + int pattern, + object value, + float[] expectedValues, + int expectedLength) + { + using var conn = new SqlConnection(s_connectionString); + await conn.OpenAsync(); + + using var insertCmd = new SqlCommand(s_insertCmdString, conn); + + SqlParameter param = pattern switch + { + 1 => new SqlParameter + { + ParameterName = s_vectorParamName, + SqlDbType = (SqlDbType)36, // SqlDbTypeExtension.Vector + Value = value + }, + 2 => new SqlParameter(s_vectorParamName, value), + 3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value }, + 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, + _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") + }; + + insertCmd.Parameters.Add(param); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + + await ValidateInsertedDataAsync(conn, expectedValues, expectedLength); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))] + [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] + public void TestStoredProcParamsForVectorFloat32( + int pattern, + object value, + float[] expectedValues, + int expectedLength) + { + //Create SP for test + using var conn = new SqlConnection(s_connectionString); + conn.Open(); + DataTestUtility.CreateSP(conn, s_storedProcName, s_storedProcBody); + using var command = new SqlCommand(s_storedProcName, conn) + { + CommandType = CommandType.StoredProcedure + }; + + // Set input and output parameters + SqlParameter inputParam = pattern switch + { + 1 => new SqlParameter + { + ParameterName = s_vectorParamName, + SqlDbType = SqlDbTypeExtensions.Vector, // SqlDbTypeExtension.Vector + Value = value + }, + 2 => new SqlParameter(s_vectorParamName, value), + 3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value }, + 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, + _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") + }; + command.Parameters.Add(inputParam); + + var outputParam = new SqlParameter + { + ParameterName = s_outputVectorParamName, + SqlDbType = SqlDbTypeExtensions.Vector, + Direction = ParameterDirection.Output, + Value = SqlVector.CreateNull(VectorFloat32TestData.vectorColumnLength) + }; + command.Parameters.Add(outputParam); + + // Execute the stored procedure + command.ExecuteNonQuery(); + + // Validate the output parameter + var vector = (SqlVector)outputParam.Value; + ValidateSqlVectorFloat32Object(vector.IsNull, vector, expectedValues, expectedLength); + + // Validate error for conventional way of setting output parameters + command.Parameters.Clear(); + command.Parameters.Add(inputParam); + var outputParamWithoutVal = new SqlParameter(s_outputVectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Direction = ParameterDirection.Output }; + command.Parameters.Add(outputParamWithoutVal); + Assert.Throws(() => command.ExecuteNonQuery()); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))] + [MemberData(nameof(VectorFloat32TestData.GetVectorFloat32TestData), MemberType = typeof(VectorFloat32TestData), DisableDiscoveryEnumeration = true)] + public async Task TestStoredProcParamsForVectorFloat32Async( + int pattern, + object value, + float[] expectedValues, + int expectedLength) + { + //Create SP for test + using var conn = new SqlConnection(s_connectionString); + await conn.OpenAsync(); + DataTestUtility.CreateSP(conn, s_storedProcName, s_storedProcBody); + using var command = new SqlCommand(s_storedProcName, conn) + { + CommandType = CommandType.StoredProcedure + }; + + // Set input and output parameters + SqlParameter inputParam = pattern switch + { + 1 => new SqlParameter + { + ParameterName = s_vectorParamName, + SqlDbType = SqlDbTypeExtensions.Vector, // SqlDbTypeExtension.Vector + Value = value + }, + 2 => new SqlParameter(s_vectorParamName, value), + 3 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector) { Value = value }, + 4 => new SqlParameter(s_vectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Value = value }, + _ => throw new ArgumentOutOfRangeException(nameof(pattern), $"Unsupported pattern: {pattern}") + }; + command.Parameters.Add(inputParam); + + var outputParam = new SqlParameter + { + ParameterName = s_outputVectorParamName, + SqlDbType = SqlDbTypeExtensions.Vector, + Direction = ParameterDirection.Output, + Value = SqlVector.CreateNull(VectorFloat32TestData.vectorColumnLength) + }; + command.Parameters.Add(outputParam); + + // Execute the stored procedure + await command.ExecuteNonQueryAsync(); + + // Validate the output parameter + var vector = (SqlVector)outputParam.Value; + ValidateSqlVectorFloat32Object(vector.IsNull, vector, expectedValues, expectedLength); + + // Validate error for conventional way of setting output parameters + command.Parameters.Clear(); + command.Parameters.Add(inputParam); + var outputParamWithoutVal = new SqlParameter(s_outputVectorParamName, SqlDbTypeExtensions.Vector, VectorFloat32TestData.IncorrectParamSize) { Direction = ParameterDirection.Output }; + command.Parameters.Add(outputParamWithoutVal); + await Assert.ThrowsAsync(async () => await command.ExecuteNonQueryAsync()); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))] + [InlineData(1)] + [InlineData(2)] + public void TestBulkCopyFromSqlTable(int bulkCopySourceMode) + { + //Setup source with test data and create destination table for bulkcopy. + SqlConnection sourceConnection = new SqlConnection(s_connectionString); + sourceConnection.Open(); + SqlConnection destinationConnection = new SqlConnection(s_connectionString); + destinationConnection.Open(); + DataTable table = null; + switch (bulkCopySourceMode) + { + + case 1: + // Use SqlServer table as source + var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values (@VectorData)", sourceConnection); + var vectorParam = new SqlParameter(s_vectorParamName, new SqlVector(VectorFloat32TestData.testData)); + + // Insert 2 rows with one non-null and null value + insertCmd.Parameters.Add(vectorParam); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + vectorParam.Value = DBNull.Value; + insertCmd.Parameters.Add(vectorParam); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + break; + case 2: + table = new DataTable(s_bulkCopySrcTableName); + table.Columns.Add("Id", typeof(int)); + table.Columns.Add("VectorData", typeof(SqlVector)); + table.Rows.Add(1, new SqlVector(VectorFloat32TestData.testData)); + table.Rows.Add(2, DBNull.Value); + break; + default: + throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); + } + + + + //Bulkcopy from sql server table to destination table + using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, VectorData FROM {s_bulkCopySrcTableName}", sourceConnection); + using SqlDataReader reader = sourceDataCommand.ExecuteReader(); + + // Verify that the destination table is empty before bulk copy + using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {s_tableName}", destinationConnection); + Assert.Equal(0, Convert.ToInt16(countCommand.ExecuteScalar())); + + // Initialize bulk copy configuration + using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) + { + DestinationTableName = s_tableName, + }; + + try + { + switch (bulkCopySourceMode) + { + case 1: + bulkCopy.WriteToServer(reader); + break; + case 2: + bulkCopy.WriteToServer(table); + break; + default: + throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); + } + } + catch (Exception ex) + { + // If bulk copy fails, fail the test with the exception message + Assert.Fail($"Bulk copy failed: {ex.Message}"); + } + + // Verify that the 2 rows from the source table have been copied into the destination table. + Assert.Equal(2, Convert.ToInt16(countCommand.ExecuteScalar())); + + // Read the data from destination table as varbinary to verify the UTF-8 byte sequence + using SqlCommand verifyCommand = new SqlCommand($"SELECT VectorData from {s_tableName}", destinationConnection); + using SqlDataReader verifyReader = verifyCommand.ExecuteReader(); + + // Verify that we have data in the destination table + Assert.True(verifyReader.Read(), "No data found in destination table after bulk copy."); + + // Validate first non-null value. + Assert.True(!verifyReader.IsDBNull(0), "First row in the table is null."); + Assert.Equal(VectorFloat32TestData.testData, ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); + Assert.Equal(VectorFloat32TestData.testData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); + + // Verify that we have another row + Assert.True(verifyReader.Read(), "Second row not found in the table"); + + // Verify that we have encountered null. + Assert.True(verifyReader.IsDBNull(0)); + Assert.Equal(Array.Empty(), ((SqlVector)verifyReader.GetSqlVector(0)).Memory.ToArray()); + Assert.Equal(VectorFloat32TestData.testData.Length, ((SqlVector)verifyReader.GetSqlVector(0)).Length); + } + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))] + [InlineData(1)] + [InlineData(2)] + public async Task TestBulkCopyFromSqlTableAsync(int bulkCopySourceMode) + { + //Setup source with test data and create destination table for bulkcopy. + SqlConnection sourceConnection = new SqlConnection(s_connectionString); + await sourceConnection.OpenAsync(); + SqlConnection destinationConnection = new SqlConnection(s_connectionString); + await destinationConnection.OpenAsync(); + + DataTable table = null; + switch (bulkCopySourceMode) + { + + case 1: + // Use SqlServer table as source + var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values (@VectorData)", sourceConnection); + var vectorParam = new SqlParameter(s_vectorParamName, new SqlVector(VectorFloat32TestData.testData)); + + // Insert 2 rows with one non-null and null value + insertCmd.Parameters.Add(vectorParam); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + vectorParam.Value = DBNull.Value; + insertCmd.Parameters.Add(vectorParam); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + break; + case 2: + table = new DataTable(s_bulkCopySrcTableName); + table.Columns.Add("Id", typeof(int)); + table.Columns.Add("VectorData", typeof(SqlVector)); + table.Rows.Add(1, new SqlVector(VectorFloat32TestData.testData)); + table.Rows.Add(2, DBNull.Value); + break; + default: + throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); + } + + //Bulkcopy from sql server table to destination table + using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, VectorData FROM {s_bulkCopySrcTableName}", sourceConnection); + using SqlDataReader reader = await sourceDataCommand.ExecuteReaderAsync(); + + // Verify that the destination table is empty before bulk copy + using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {s_tableName}", destinationConnection); + Assert.Equal(0, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); + + // Initialize bulk copy configuration + using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) + { + DestinationTableName = s_tableName, + }; + + try + { // Perform bulkcopy + switch (bulkCopySourceMode) + { + case 1: + await bulkCopy.WriteToServerAsync(reader); + break; + case 2: + await bulkCopy.WriteToServerAsync(table); + break; + default: + throw new ArgumentOutOfRangeException(nameof(bulkCopySourceMode), $"Unsupported bulk copy source mode: {bulkCopySourceMode}"); + } + } + catch (Exception ex) + { + // If bulk copy fails, fail the test with the exception message + Assert.Fail($"Bulk copy failed: {ex.Message}"); + } + + // Verify that the 2 rows from the source table have been copied into the destination table. + Assert.Equal(2, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); + + // Read the data from destination table as varbinary to verify the UTF-8 byte sequence + using SqlCommand verifyCommand = new SqlCommand($"SELECT VectorData from {s_tableName}", destinationConnection); + using SqlDataReader verifyReader = await verifyCommand.ExecuteReaderAsync(); + + // Verify that we have data in the destination table + Assert.True(await verifyReader.ReadAsync(), "No data found in destination table after bulk copy."); + + // Validate first non-null value. + Assert.True(!await verifyReader.IsDBNullAsync(0), "First row in the table is null."); + var vector = await verifyReader.GetFieldValueAsync>(0); + Assert.Equal(VectorFloat32TestData.testData, vector.Memory.ToArray()); + Assert.Equal(VectorFloat32TestData.testData.Length, vector.Length); + + // Verify that we have another row + Assert.True(await verifyReader.ReadAsync(), "Second row not found in the table"); + + // Verify that we have encountered null. + Assert.True(await verifyReader.IsDBNullAsync(0)); + vector = await verifyReader.GetFieldValueAsync>(0); + Assert.Equal(Array.Empty(), vector.Memory.ToArray()); + Assert.Equal(VectorFloat32TestData.testData.Length, vector.Length); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))] + public void TestInsertVectorsFloat32WithPrepare() + { + SqlConnection conn = new SqlConnection(s_connectionString); + conn.Open(); + SqlCommand command = new SqlCommand(s_insertCmdString, conn); + SqlParameter vectorParam = new SqlParameter("@VectorData", SqlDbTypeExtensions.Vector); + command.Parameters.Add(vectorParam); + command.Prepare(); + for (int i = 0; i < 10; i++) + { + vectorParam.Value = new SqlVector(new float[] { i + 0.1f, i + 0.2f, i + 0.3f }); + command.ExecuteNonQuery(); + } + SqlCommand validateCommand = new SqlCommand($"SELECT VectorData FROM {s_tableName}", conn); + using SqlDataReader reader = validateCommand.ExecuteReader(); + int rowcnt = 0; + while (reader.Read()) + { + float[] expectedData = new float[] { rowcnt + 0.1f, rowcnt + 0.2f, rowcnt + 0.3f }; + float[] dbData = reader.GetSqlVector(0).Memory.ToArray(); + Assert.Equal(expectedData, dbData); + rowcnt++; + } + Assert.Equal(10, rowcnt); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorAPIValidationTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorAPIValidationTest.cs new file mode 100644 index 0000000000..84c16e1793 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorAPIValidationTest.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.Data.SqlTypes; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest +{ + public sealed class VectorAPIValidationTest + { + // We need this testcase to validate ref assembly for vector APIs + // Unit tests are covered under SqlVectorTest.cs + [Fact] + public void VectorAPITest() + { + // Validate that SqlVector is a valid type and has valid SqlDbType + Assert.True(typeof(SqlVector).IsValueType, "SqlVector should be a value type."); + Assert.Equal(36, (int)SqlDbTypeExtensions.Vector); + + // Validate ctor1 with float[] : public SqlVector(System.ReadOnlyMemory memory) { } + var vector = new SqlVector(VectorFloat32TestData.testData); + Assert.Equal(VectorFloat32TestData.testData, vector.Memory.ToArray()); + Assert.Equal(3, vector.Length); + + // Validate ctor2 with ReadOnlyMemory : public SqlVector(ReadOnlyMemory memory) { } + vector = new SqlVector(new ReadOnlyMemory(VectorFloat32TestData.testData)); + Assert.Equal(VectorFloat32TestData.testData, vector.Memory.ToArray()); + Assert.Equal(3, vector.Length); + + //Validate IsNull property + Assert.False(vector.IsNull, "IsNull should be false for non-null vector."); + + // Validate Null property returns null + Assert.Null(SqlVector.Null); + + //Validate length property + Assert.Equal(3, vector.Length); + + // Validate CreateNull method + vector = SqlVector.CreateNull(5); + Assert.True(vector.IsNull); + Assert.Equal(5, vector.Length); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorTypeBackwardCompatibilityTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorTypeBackwardCompatibilityTests.cs new file mode 100644 index 0000000000..5fb9cf7625 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorTypeBackwardCompatibilityTests.cs @@ -0,0 +1,616 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlTypes; +using System.Text.Json; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.VectorTest +{ + public sealed class VectorTypeBackwardCompatibilityTests : IDisposable + { + private readonly ITestOutputHelper _output; + private static readonly string s_connectionString = ManualTesting.Tests.DataTestUtility.TCPConnectionString; + private static readonly string s_tableName = DataTestUtility.GetUniqueName("VectorTestTable"); + private static readonly string s_bulkCopySrcTableName = DataTestUtility.GetUniqueName("VectorBulkCopyTestTable"); + private static readonly string s_bulkCopySrcTableDef = $@"(Id INT PRIMARY KEY IDENTITY, VectorData varchar(max) NULL)"; + private static readonly string s_tableDefinition = $@"(Id INT PRIMARY KEY IDENTITY, VectorData vector(3) NULL)"; + private static readonly string s_selectCmdString = $"SELECT VectorData FROM {s_tableName} ORDER BY Id DESC"; + private static readonly string s_insertCmdString = $"INSERT INTO {s_tableName} (VectorData) VALUES (@VectorData)"; + private static readonly string s_vectorParamName = $"@VectorData"; + private static readonly string s_storedProcName = DataTestUtility.GetUniqueName("VectorsAsVarcharSp"); + private static readonly string s_storedProcBody = $@" + @InputVectorJson VARCHAR(MAX), -- Input: Serialized float[] as JSON string + @OutputVectorJson VARCHAR(MAX) OUTPUT -- Output: Echoed back from latest inserted row + AS + BEGIN + SET NOCOUNT ON; + + -- Insert into vector table + INSERT INTO {s_tableName} (VectorData) + VALUES (@InputVectorJson); + + -- Retrieve latest entry (assumes auto-incrementing ID) + SELECT TOP 1 @OutputVectorJson = VectorData + FROM {s_tableName} + ORDER BY Id DESC; + END;"; + + public VectorTypeBackwardCompatibilityTests(ITestOutputHelper output) + { + _output = output; + using var connection = new SqlConnection(s_connectionString); + connection.Open(); + DataTestUtility.CreateTable(connection, s_tableName, s_tableDefinition); + DataTestUtility.CreateTable(connection, s_bulkCopySrcTableName, s_bulkCopySrcTableDef); + DataTestUtility.CreateSP(connection, s_storedProcName, s_storedProcBody); + } + + public void Dispose() + { + using var connection = new SqlConnection(s_connectionString); + connection.Open(); + DataTestUtility.DropTable(connection, s_tableName); + DataTestUtility.DropTable(connection, s_bulkCopySrcTableName); + DataTestUtility.DropStoredProcedure(connection, s_storedProcName); + } + + private void ValidateInsertedData(SqlConnection connection, float[] expectedData) + { + using var selectCmd = new SqlCommand(s_selectCmdString, connection); + using var reader = selectCmd.ExecuteReader(); + Assert.True(reader.Read(), "No data found in the table."); + + if (!reader.IsDBNull(0)) + { + string jsonFromDb = reader.GetString(0); + float[] deserialized = JsonSerializer.Deserialize(jsonFromDb)!; + Assert.Equal(expectedData, deserialized); + } + else + { + Assert.Null(expectedData); + var val = reader.GetValue(0); + Assert.Equal(DBNull.Value, val); + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))] + public void TestVectorDataInsertionAsVarchar() + { + float[] data = { 1.1f, 2.2f, 3.3f }; + string json = JsonSerializer.Serialize(data); + + using var conn = new SqlConnection(s_connectionString); + conn.Open(); + + using var insertCmd = new SqlCommand(s_insertCmdString, conn); + + // Pattern 1: Default constructor + property setters + var p1 = new SqlParameter(); + p1.ParameterName = s_vectorParamName; + p1.SqlDbType = SqlDbType.VarChar; + p1.Size = -1; //varchar(max) + p1.Value = json; + insertCmd.Parameters.Add(p1); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + ValidateInsertedData(conn, data); + + var nullp1 = new SqlParameter(); + nullp1.ParameterName = s_vectorParamName; + nullp1.SqlDbType = SqlDbType.VarChar; + nullp1.Size = -1; //varchar(max) + nullp1.Value = DBNull.Value; + insertCmd.Parameters.Add(nullp1); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + ValidateInsertedData(conn, null); + + // Pattern 2: Name + value constructor + var p2 = new SqlParameter(s_vectorParamName, json); + insertCmd.Parameters.Add(p2); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + ValidateInsertedData(conn, data); + + var nullp2 = new SqlParameter(s_vectorParamName, DBNull.Value); + insertCmd.Parameters.Add(nullp2); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + ValidateInsertedData(conn, null); + + // Pattern 3: Name + SqlDbType constructor + var p3 = new SqlParameter(s_vectorParamName, SqlDbType.VarChar) { Value = json }; + insertCmd.Parameters.Add(p3); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + ValidateInsertedData(conn, data); + + var nullp3 = new SqlParameter(s_vectorParamName, SqlDbType.VarChar) { Value = DBNull.Value }; + insertCmd.Parameters.Add(nullp3); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + ValidateInsertedData(conn, null); + + // Pattern 4: Name + SqlDbType + Size constructor + var p4 = new SqlParameter(s_vectorParamName, SqlDbType.VarChar, -1) { Value = json }; + insertCmd.Parameters.Add(p4); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + ValidateInsertedData(conn, data); + + var nullp4 = new SqlParameter(s_vectorParamName, SqlDbType.VarChar, -1) { Value = DBNull.Value }; + insertCmd.Parameters.Add(nullp4); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + ValidateInsertedData(conn, null); + } + + private async Task ValidateInsertedDataAsync(SqlConnection connection, float[] expectedData) + { + using var selectCmd = new SqlCommand(s_selectCmdString, connection); + using var reader = await selectCmd.ExecuteReaderAsync(); + Assert.True(await reader.ReadAsync(), "No data found in the table."); + + if (!await reader.IsDBNullAsync(0)) + { + string jsonFromDb = reader.GetString(0); + float[] deserialized = JsonSerializer.Deserialize(jsonFromDb)!; + Assert.Equal(expectedData, deserialized); + } + else + { + Assert.Null(expectedData); + var val = reader.GetValue(0); + Assert.Equal(DBNull.Value, val); + } + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))] + public async Task TestVectorParameterInitializationAsync() + { + float[] data = { 1.1f, 2.2f, 3.3f }; + string json = JsonSerializer.Serialize(data); + + using var conn = new SqlConnection(s_connectionString); + await conn.OpenAsync(); + + using var insertCmd = new SqlCommand(s_insertCmdString, conn); + + // Pattern 1: Default constructor + property setters + var p1 = new SqlParameter(); + p1.ParameterName = s_vectorParamName; + p1.SqlDbType = SqlDbType.VarChar; + p1.Size = -1; + p1.Value = json; + insertCmd.Parameters.Add(p1); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + await ValidateInsertedDataAsync(conn, data); + + var nullp1 = new SqlParameter(); + nullp1.ParameterName = s_vectorParamName; + nullp1.SqlDbType = SqlDbType.VarChar; + nullp1.Size = -1; + nullp1.Value = DBNull.Value; + insertCmd.Parameters.Add(nullp1); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + await ValidateInsertedDataAsync(conn, null); + + // Pattern 2: Name + value constructor + var p2 = new SqlParameter(s_vectorParamName, json); + insertCmd.Parameters.Add(p2); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + await ValidateInsertedDataAsync(conn, data); + + var nullp2 = new SqlParameter(s_vectorParamName, DBNull.Value); + insertCmd.Parameters.Add(nullp2); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + await ValidateInsertedDataAsync(conn, null); + + // Pattern 3: Name + SqlDbType constructor + var p3 = new SqlParameter(s_vectorParamName, SqlDbType.VarChar) { Value = json }; + insertCmd.Parameters.Add(p3); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + await ValidateInsertedDataAsync(conn, data); + + var nullp3 = new SqlParameter(s_vectorParamName, SqlDbType.VarChar) { Value = DBNull.Value }; + insertCmd.Parameters.Add(nullp3); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + await ValidateInsertedDataAsync(conn, null); + + // Pattern 4: Name + SqlDbType + Size constructor + var p4 = new SqlParameter(s_vectorParamName, SqlDbType.VarChar, -1) { Value = json }; + insertCmd.Parameters.Add(p4); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + await ValidateInsertedDataAsync(conn, data); + + var nullp4 = new SqlParameter(s_vectorParamName, SqlDbType.VarChar, -1) { Value = DBNull.Value }; + insertCmd.Parameters.Add(nullp4); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + await ValidateInsertedDataAsync(conn, null); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))] + public void TestVectorDataReadsAsVarchar() + { + float[] data = { 1.1f, 2.2f, 3.3f }; + string dataAsJson = JsonSerializer.Serialize(data); + + using var conn = new SqlConnection(s_connectionString); + conn.Open(); + + //Insert non-null values and validate APIs for reading vector data as varchar(max) + using var insertCmd = new SqlCommand(s_insertCmdString, conn); + var p1 = new SqlParameter(s_vectorParamName, SqlDbType.VarChar, -1) { Value = dataAsJson }; + insertCmd.Parameters.Add(p1); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + + //Validate Reader + using SqlCommand verifyCommand = new SqlCommand(s_selectCmdString, conn); + var reader = verifyCommand.ExecuteReader(); + Assert.True(reader.Read(), "No data found in the table."); + + //Read using GetString + string result = reader.GetString(0); + float[] dbData = JsonSerializer.Deserialize(result)!; + Assert.Equal(data, dbData); + + //Read using GetSqlString + result = reader.GetSqlString(0).Value; + dbData = JsonSerializer.Deserialize(result)!; + Assert.Equal(data, dbData); + + //Read using GetFieldValue + result = reader.GetFieldValue(0); + dbData = JsonSerializer.Deserialize(result)!; + Assert.Equal(data, dbData); + + reader.Close(); + + // Validate For Null Value + insertCmd.Parameters.Clear(); + p1.Value = DBNull.Value; + insertCmd.Parameters.Add(p1); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + + //Validate Reader for null value + reader = verifyCommand.ExecuteReader(); + Assert.True(reader.Read(), "No data found in the table."); + + //Read using GetString + Assert.Throws(() => reader.GetString(0)); + + //Read using GetSqlString + Assert.Throws(() => reader.GetString(0)); + + //Read using GetFieldValue + Assert.Throws(() => reader.GetFieldValue(0)); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))] + public async Task TestVectorDataReadsAsVarcharAsync() + { + float[] data = { 1.1f, 2.2f, 3.3f }; + string dataAsJson = JsonSerializer.Serialize(data); + + using var conn = new SqlConnection(s_connectionString); + await conn.OpenAsync(); + + //Insert non-null values and validate APIs for reading vector data as varchar(max) + using var insertCmd = new SqlCommand(s_insertCmdString, conn); + var p1 = new SqlParameter(s_vectorParamName, SqlDbType.VarChar, -1) { Value = dataAsJson }; + insertCmd.Parameters.Add(p1); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + + //Validate Reader + using SqlCommand verifyCommand = new SqlCommand(s_selectCmdString, conn); + using var reader = await verifyCommand.ExecuteReaderAsync(); + Assert.True(await reader.ReadAsync(), "No data found in the table."); + + //Read using GetString + string result = reader.GetString(0); + float[] dbData = JsonSerializer.Deserialize(result)!; + Assert.Equal(data, dbData); + + //Read using GetSqlString + result = reader.GetSqlString(0).Value; + dbData = JsonSerializer.Deserialize(result)!; + Assert.Equal(data, dbData); + + //Read using GetFieldValue + result = await reader.GetFieldValueAsync(0); + dbData = JsonSerializer.Deserialize(result)!; + Assert.Equal(data, dbData); + + reader.Close(); + + // Validate For Null Value + insertCmd.Parameters.Clear(); + p1.Value = DBNull.Value; + insertCmd.Parameters.Add(p1); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + + //Validate Reader for null value + var reader2 = await verifyCommand.ExecuteReaderAsync(); + Assert.True(await reader2.ReadAsync(), "No data found in the table."); + + //Read using GetString + Assert.Throws(() => reader2.GetString(0)); + + //Read using GetSqlString + Assert.Throws(() => reader2.GetString(0)); + + //Read using GetFieldValueAsync + await Assert.ThrowsAsync(async () => await reader2.GetFieldValueAsync(0)); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))] + public void TestStoredProcParamsForVectorAsVarchar() + { + // Test data + float[] data = { 7.1f, 8.2f, 9.3f }; + string dataAsJson = JsonSerializer.Serialize(data); + + //Create SP for test + using var conn = new SqlConnection(s_connectionString); + conn.Open(); + DataTestUtility.CreateSP(conn, s_storedProcName, s_storedProcBody); + using var command = new SqlCommand(s_storedProcName, conn) + { + CommandType = CommandType.StoredProcedure + }; + + // Set input and output parameters + var inputParam = new SqlParameter("@InputVectorJson", SqlDbType.VarChar, -1); + inputParam.Value = dataAsJson; + command.Parameters.Add(inputParam); + var outputParam = new SqlParameter("@OutputVectorJson", SqlDbType.VarChar, -1) + { + Direction = ParameterDirection.Output + }; + command.Parameters.Add(outputParam); + + // Execute the stored procedure + command.ExecuteNonQuery(); + + // Validate the output parameter + var dbDataAsJson = outputParam.Value as string; + float[] dbData = JsonSerializer.Deserialize(dbDataAsJson)!; + Assert.NotNull(dbDataAsJson); + Assert.Equal(data, dbData); + + // Test with null value + command.Parameters.Clear(); + inputParam.Value = DBNull.Value; + command.Parameters.Add(inputParam); + command.Parameters.Add(outputParam); + command.ExecuteNonQuery(); + + // Validate output paramter for null value + Assert.True(outputParam.Value == DBNull.Value); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))] + public async Task TestStoredProcParamsForVectorAsVarcharAsync() + { + // Test data + float[] data = { 7.1f, 8.2f, 9.3f }; + string dataAsJson = JsonSerializer.Serialize(data); + + // Create SP for test + using var conn = new SqlConnection(s_connectionString); + await conn.OpenAsync(); + DataTestUtility.CreateSP(conn, s_storedProcName, s_storedProcBody); + + using var command = new SqlCommand(s_storedProcName, conn) + { + CommandType = CommandType.StoredProcedure + }; + + // Set input and output parameters + var inputParam = new SqlParameter("@InputVectorJson", SqlDbType.VarChar, -1) + { + Value = dataAsJson + }; + command.Parameters.Add(inputParam); + + var outputParam = new SqlParameter("@OutputVectorJson", SqlDbType.VarChar, -1) + { + Direction = ParameterDirection.Output + }; + command.Parameters.Add(outputParam); + + // Execute the stored procedure + await command.ExecuteNonQueryAsync(); + + // Validate the output parameter + var dbDataAsJson = outputParam.Value as string; + float[] dbData = JsonSerializer.Deserialize(dbDataAsJson)!; + Assert.NotNull(dbDataAsJson); + Assert.Equal(data, dbData); + + // Test with null value + command.Parameters.Clear(); + inputParam.Value = DBNull.Value; + command.Parameters.Add(inputParam); + command.Parameters.Add(outputParam); + + await command.ExecuteNonQueryAsync(); + + // Validate output parameter for null value + Assert.True(outputParam.Value == DBNull.Value); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))] + public void TestSqlBulkCopyForVectorAsVarchar() + { + //Setup source with test data and create destination table for bulkcopy. + SqlConnection sourceConnection = new SqlConnection(s_connectionString); + sourceConnection.Open(); + SqlConnection destinationConnection = new SqlConnection(s_connectionString); + destinationConnection.Open(); + float[] testData = { 1.1f, 2.2f, 3.3f }; + string testDataAsJson = JsonSerializer.Serialize(testData); + using var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values (@VectorData)", sourceConnection); + var varcharVectorParam = new SqlParameter(s_vectorParamName, SqlDbType.VarChar, -1) { Value = testDataAsJson }; + insertCmd.Parameters.Add(varcharVectorParam); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + insertCmd.Parameters.Clear(); + varcharVectorParam.Value = DBNull.Value; + insertCmd.Parameters.Add(varcharVectorParam); + Assert.Equal(1, insertCmd.ExecuteNonQuery()); + + //Bulkcopy from sql server table to destination table + using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, VectorData FROM {s_bulkCopySrcTableName}", sourceConnection); + using SqlDataReader reader = sourceDataCommand.ExecuteReader(); + + // Verify that the destination table is empty before bulk copy + using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {s_tableName}", destinationConnection); + Assert.Equal(0, Convert.ToInt16(countCommand.ExecuteScalar())); + + // Initialize bulk copy configuration + using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) + { + DestinationTableName = s_tableName, + }; + + try + { + // Perform bulk copy from source to destination table + bulkCopy.WriteToServer(reader); + } + catch (Exception ex) + { + // If bulk copy fails, fail the test with the exception message + Assert.Fail($"Bulk copy failed: {ex.Message}"); + } + + // Verify that the 2 rows from the source table have been copied into the destination table. + Assert.Equal(2, Convert.ToInt16(countCommand.ExecuteScalar())); + + // Read the data from destination table as varbinary to verify the UTF-8 byte sequence + using SqlCommand verifyCommand = new SqlCommand($"SELECT VectorData from {s_tableName}", destinationConnection); + using SqlDataReader verifyReader = verifyCommand.ExecuteReader(); + + // Verify that we have data in the destination table + Assert.True(verifyReader.Read(), "No data found in destination table after bulk copy."); + + // Validate first non-null value. + Assert.True(!verifyReader.IsDBNull(0), "First row in the table is null."); + Assert.Equal(testData, JsonSerializer.Deserialize(verifyReader.GetString(0))); + + // Verify that we have another row + Assert.True(verifyReader.Read(), "Second row not found in the table"); + + // Verify that we have encountered null. + Assert.True(verifyReader.IsDBNull(0)); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))] + public async Task TestSqlBulkCopyForVectorAsVarcharAsync() + { + //Setup source with test data and create destination table for bulkcopy. + SqlConnection sourceConnection = new SqlConnection(s_connectionString); + await sourceConnection.OpenAsync(); + SqlConnection destinationConnection = new SqlConnection(s_connectionString); + await destinationConnection.OpenAsync(); + float[] testData = { 1.1f, 2.2f, 3.3f }; + string testDataAsJson = JsonSerializer.Serialize(testData); + using var insertCmd = new SqlCommand($"insert into {s_bulkCopySrcTableName} values (@VectorData)", sourceConnection); + var varcharVectorParam = new SqlParameter(s_vectorParamName, SqlDbType.VarChar, -1) { Value = testDataAsJson }; + insertCmd.Parameters.Add(varcharVectorParam); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + insertCmd.Parameters.Clear(); + varcharVectorParam.Value = DBNull.Value; + insertCmd.Parameters.Add(varcharVectorParam); + Assert.Equal(1, await insertCmd.ExecuteNonQueryAsync()); + + //Bulkcopy from sql server table to destination table + using SqlCommand sourceDataCommand = new SqlCommand($"SELECT Id, VectorData FROM {s_bulkCopySrcTableName}", sourceConnection); + using SqlDataReader reader = await sourceDataCommand.ExecuteReaderAsync(); + + // Verify that the destination table is empty before bulk copy + using SqlCommand countCommand = new SqlCommand($"SELECT COUNT(*) FROM {s_tableName}", destinationConnection); + Assert.Equal(0, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); + + // Initialize bulk copy configuration + using SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection) + { + DestinationTableName = s_tableName, + }; + + try + { + // Perform bulk copy from source to destination table + await bulkCopy.WriteToServerAsync(reader); + } + catch (Exception ex) + { + // If bulk copy fails, fail the test with the exception message + Assert.Fail($"Bulk copy failed: {ex.Message}"); + } + + // Verify that the 2 rows from the source table have been copied into the destination table. + Assert.Equal(2, Convert.ToInt16(await countCommand.ExecuteScalarAsync())); + + // Read the data from destination table as varbinary to verify the UTF-8 byte sequence + using SqlCommand verifyCommand = new SqlCommand($"SELECT VectorData from {s_tableName}", destinationConnection); + using SqlDataReader verifyReader = await verifyCommand.ExecuteReaderAsync(); + + // Verify that we have data in the destination table + Assert.True(await verifyReader.ReadAsync(), "No data found in destination table after bulk copy."); + + // Validate first non-null value. + Assert.True(!verifyReader.IsDBNull(0), "First row in the table is null."); + Assert.Equal(testData, JsonSerializer.Deserialize(verifyReader.GetString(0))); + + // Verify that we have another row + Assert.True(await verifyReader.ReadAsync(), "Second row not found in the table"); + + // Verify that we have encountered null. + Assert.True(await verifyReader.IsDBNullAsync(0)); + } + + [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.IsVectorSupported))] + public void TestInsertVectorsAsVarcharWithPrepare() + { + SqlConnection conn = new SqlConnection(s_connectionString); + conn.Open(); + SqlCommand command = new SqlCommand(s_insertCmdString, conn); + SqlParameter vectorParam = new SqlParameter("@VectorData", SqlDbType.VarChar, -1); + command.Parameters.Add(vectorParam); + command.Prepare(); + for (int i = 0; i < 10; i++) + { + vectorParam.Value = JsonSerializer.Serialize(new float[] { i + 0.1f, i + 0.2f, i + 0.3f }); + command.ExecuteNonQuery(); + } + SqlCommand validateCommand = new SqlCommand($"SELECT VectorData FROM {s_tableName}", conn); + using SqlDataReader reader = validateCommand.ExecuteReader(); + int rowcnt = 0; + while (reader.Read()) + { + float[] expectedData = new float[] { rowcnt + 0.1f, rowcnt + 0.2f, rowcnt + 0.3f }; + float[] dbData = JsonSerializer.Deserialize(reader.GetString(0))!; + Assert.Equal(expectedData, dbData); + rowcnt++; + } + Assert.Equal(10, rowcnt); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs index 5d5c9d5ec7..b8649d43d2 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/DiagnosticTest.cs @@ -430,7 +430,7 @@ public void ConnectionOpenTest() { sqlConnection.Open(); } - }, true); + }); return RemoteExecutor.SuccessExitCode; }).Dispose(); } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/EventCounterTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs similarity index 50% rename from src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/EventCounterTest.cs rename to src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs index a84a123dc6..4e90bbf6c7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/EventCounterTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/MetricsTest.cs @@ -9,18 +9,19 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests { - /// - /// This unit test is just valid for .NetCore 3.0 and above - /// - public class EventCounterTest + public class MetricsTest { - public EventCounterTest() +#if NETFRAMEWORK + private readonly static TraceSwitch s_perfCtrSwitch = new TraceSwitch("ConnectionPoolPerformanceCounterDetail", "level of detail to track with connection pool performance counters"); +#endif + + public MetricsTest() { ClearConnectionPools(); } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - public void EventCounter_HardConnectionsCounters_Functional() + public void NonPooledConnectionsCounters_Functional() { //create a non-pooled connection var stringBuilder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { Pooling = false }; @@ -30,30 +31,39 @@ public void EventCounter_HardConnectionsCounters_Functional() using (var conn = new SqlConnection(stringBuilder.ToString())) { - //initially we have no open physical connections - Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, - SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); + if (SupportsActiveConnectionCounters) + { + //initially we have no open physical connections + Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, + SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); + } conn.Open(); //when the connection gets opened, the real physical connection appears - Assert.Equal(ahc + 1, SqlClientEventSourceProps.ActiveHardConnections); + if (SupportsActiveConnectionCounters) + { + Assert.Equal(ahc + 1, SqlClientEventSourceProps.ActiveHardConnections); + Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, + SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); + } Assert.Equal(npc + 1, SqlClientEventSourceProps.NonPooledConnections); - Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, - SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); conn.Close(); //when the connection gets closed, the real physical connection is also closed - Assert.Equal(ahc, SqlClientEventSourceProps.ActiveHardConnections); + if (SupportsActiveConnectionCounters) + { + Assert.Equal(ahc, SqlClientEventSourceProps.ActiveHardConnections); + Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, + SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); + } Assert.Equal(npc, SqlClientEventSourceProps.NonPooledConnections); - Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, - SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); } } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - public void EventCounter_SoftConnectionsCounters_Functional() + public void PooledConnectionsCounters_Functional() { //create a pooled connection var stringBuilder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { Pooling = true }; @@ -68,42 +78,57 @@ public void EventCounter_SoftConnectionsCounters_Functional() using (var conn = new SqlConnection(stringBuilder.ToString())) { - //initially we have no open physical connections - Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, - SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); - Assert.Equal(SqlClientEventSourceProps.ActiveSoftConnections, - SqlClientEventSourceProps.SoftConnects - SqlClientEventSourceProps.SoftDisconnects); + if (SupportsActiveConnectionCounters) + { + //initially we have no open physical connections + Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, + SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); + Assert.Equal(SqlClientEventSourceProps.ActiveSoftConnections, + SqlClientEventSourceProps.SoftConnects - SqlClientEventSourceProps.SoftDisconnects); + } conn.Open(); //when the connection gets opened, the real physical connection appears //and the appropriate pooling infrastructure gets deployed - Assert.Equal(ahc + 1, SqlClientEventSourceProps.ActiveHardConnections); - Assert.Equal(asc + 1, SqlClientEventSourceProps.ActiveSoftConnections); + if (SupportsActiveConnectionCounters) + { + Assert.Equal(ahc + 1, SqlClientEventSourceProps.ActiveHardConnections); + Assert.Equal(asc + 1, SqlClientEventSourceProps.ActiveSoftConnections); + Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, + SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); + Assert.Equal(SqlClientEventSourceProps.ActiveSoftConnections, + SqlClientEventSourceProps.SoftConnects - SqlClientEventSourceProps.SoftDisconnects); + } Assert.Equal(pc + 1, SqlClientEventSourceProps.PooledConnections); Assert.Equal(npc, SqlClientEventSourceProps.NonPooledConnections); Assert.Equal(acp + 1, SqlClientEventSourceProps.ActiveConnectionPools); - Assert.Equal(ac + 1, SqlClientEventSourceProps.ActiveConnections); - Assert.Equal(fc, SqlClientEventSourceProps.FreeConnections); - Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, - SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); - Assert.Equal(SqlClientEventSourceProps.ActiveSoftConnections, - SqlClientEventSourceProps.SoftConnects - SqlClientEventSourceProps.SoftDisconnects); + if (VerboseActiveConnectionCountersEnabled) + { + Assert.Equal(ac + 1, SqlClientEventSourceProps.ActiveConnections); + Assert.Equal(fc, SqlClientEventSourceProps.FreeConnections); + } conn.Close(); //when the connection gets closed, the real physical connection gets returned to the pool - Assert.Equal(ahc + 1, SqlClientEventSourceProps.ActiveHardConnections); - Assert.Equal(asc, SqlClientEventSourceProps.ActiveSoftConnections); + if (SupportsActiveConnectionCounters) + { + Assert.Equal(ahc + 1, SqlClientEventSourceProps.ActiveHardConnections); + Assert.Equal(asc, SqlClientEventSourceProps.ActiveSoftConnections); + Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, + SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); + Assert.Equal(SqlClientEventSourceProps.ActiveSoftConnections, + SqlClientEventSourceProps.SoftConnects - SqlClientEventSourceProps.SoftDisconnects); + } Assert.Equal(pc + 1, SqlClientEventSourceProps.PooledConnections); Assert.Equal(npc, SqlClientEventSourceProps.NonPooledConnections); Assert.Equal(acp + 1, SqlClientEventSourceProps.ActiveConnectionPools); - Assert.Equal(ac, SqlClientEventSourceProps.ActiveConnections); - Assert.Equal(fc + 1, SqlClientEventSourceProps.FreeConnections); - Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, - SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); - Assert.Equal(SqlClientEventSourceProps.ActiveSoftConnections, - SqlClientEventSourceProps.SoftConnects - SqlClientEventSourceProps.SoftDisconnects); + if (VerboseActiveConnectionCountersEnabled) + { + Assert.Equal(ac, SqlClientEventSourceProps.ActiveConnections); + Assert.Equal(fc + 1, SqlClientEventSourceProps.FreeConnections); + } } using (var conn2 = new SqlConnection(stringBuilder.ToString())) @@ -111,22 +136,28 @@ public void EventCounter_SoftConnectionsCounters_Functional() conn2.Open(); //the next open connection will reuse the underlying physical connection - Assert.Equal(ahc + 1, SqlClientEventSourceProps.ActiveHardConnections); - Assert.Equal(asc + 1, SqlClientEventSourceProps.ActiveSoftConnections); + if (SupportsActiveConnectionCounters) + { + Assert.Equal(ahc + 1, SqlClientEventSourceProps.ActiveHardConnections); + Assert.Equal(asc + 1, SqlClientEventSourceProps.ActiveSoftConnections); + Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, + SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); + Assert.Equal(SqlClientEventSourceProps.ActiveSoftConnections, + SqlClientEventSourceProps.SoftConnects - SqlClientEventSourceProps.SoftDisconnects); + } Assert.Equal(pc + 1, SqlClientEventSourceProps.PooledConnections); Assert.Equal(npc, SqlClientEventSourceProps.NonPooledConnections); Assert.Equal(acp + 1, SqlClientEventSourceProps.ActiveConnectionPools); - Assert.Equal(ac + 1, SqlClientEventSourceProps.ActiveConnections); - Assert.Equal(fc, SqlClientEventSourceProps.FreeConnections); - Assert.Equal(SqlClientEventSourceProps.ActiveHardConnections, - SqlClientEventSourceProps.HardConnects - SqlClientEventSourceProps.HardDisconnects); - Assert.Equal(SqlClientEventSourceProps.ActiveSoftConnections, - SqlClientEventSourceProps.SoftConnects - SqlClientEventSourceProps.SoftDisconnects); + if (VerboseActiveConnectionCountersEnabled) + { + Assert.Equal(ac + 1, SqlClientEventSourceProps.ActiveConnections); + Assert.Equal(fc, SqlClientEventSourceProps.FreeConnections); + } } } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] - public void EventCounter_StasisCounters_Functional() + public void StasisCounters_Functional() { var stringBuilder = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) { Pooling = false, Enlist = false }; @@ -146,8 +177,9 @@ public void EventCounter_StasisCounters_Functional() Assert.Equal(0, SqlClientEventSourceProps.StasisConnections); } + [ActiveIssue("https://github.com/dotnet/SqlClient/issues/3031")] [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - public void EventCounter_ReclaimedConnectionsCounter_Functional() + public void ReclaimedConnectionsCounter_Functional() { // clean pools and pool groups ClearConnectionPools(); @@ -179,7 +211,7 @@ public void EventCounter_ReclaimedConnectionsCounter_Functional() } [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] - public void EventCounter_ConnectionPoolGroupsCounter_Functional() + public void ConnectionPoolGroupsCounter_Functional() { SqlConnection.ClearAllPools(); @@ -263,123 +295,139 @@ private static void PruneConnectionPoolGroups() private static FieldInfo GetConnectionFactoryField() { +#if NET FieldInfo connectionFactoryField = typeof(SqlConnection).GetField("s_connectionFactory", BindingFlags.Static | BindingFlags.NonPublic); +#else + FieldInfo connectionFactoryField = + typeof(SqlConnection).GetField("_connectionFactory", BindingFlags.Static | BindingFlags.NonPublic); +#endif Debug.Assert(connectionFactoryField != null); return connectionFactoryField; } + + // Only the .NET Core build supports the active-hard-connections and active-soft-connects counters. The .NET Framework + // build doesn't have comparable performance counters. + private static bool SupportsActiveConnectionCounters => +#if NET + true; +#else + false; +#endif + + private static bool VerboseActiveConnectionCountersEnabled => + SupportsActiveConnectionCounters || +#if NET + true; +#else + s_perfCtrSwitch.Level == TraceLevel.Verbose; +#endif } internal static class SqlClientEventSourceProps { private static readonly object s_log; - private static readonly FieldInfo _activeHardConnectionsCounter; - private static readonly FieldInfo _hardConnectsCounter; - private static readonly FieldInfo _hardDisconnectsCounter; - private static readonly FieldInfo _activeSoftConnectionsCounter; - private static readonly FieldInfo _softConnectsCounter; - private static readonly FieldInfo _softDisconnectsCounter; - private static readonly FieldInfo _nonPooledConnectionsCounter; - private static readonly FieldInfo _pooledConnectionsCounter; - private static readonly FieldInfo _activeConnectionPoolGroupsCounter; - private static readonly FieldInfo _inactiveConnectionPoolGroupsCounter; - private static readonly FieldInfo _activeConnectionPoolsCounter; - private static readonly FieldInfo _inactiveConnectionPoolsCounter; - private static readonly FieldInfo _activeConnectionsCounter; - private static readonly FieldInfo _freeConnectionsCounter; - private static readonly FieldInfo _stasisConnectionsCounter; - private static readonly FieldInfo _reclaimedConnectionsCounter; + private static readonly Func s_getActiveHardConnections; + private static readonly Func s_getHardConnects; + private static readonly Func s_getHardDisconnects; + private static readonly Func s_getActiveSoftConnections; + private static readonly Func s_getSoftConnects; + private static readonly Func s_getSoftDisconnects; + private static readonly Func s_getNonPooledConnections; + private static readonly Func s_getPooledConnections; + private static readonly Func s_getActiveConnectionPoolGroups; + private static readonly Func s_getInactiveConnectionPoolGroups; + private static readonly Func s_getActiveConnectionPools; + private static readonly Func s_getInactiveConnectionPools; + private static readonly Func s_getActiveConnections; + private static readonly Func s_getFreeConnections; + private static readonly Func s_getStasisConnections; + private static readonly Func s_getReclaimedConnections; static SqlClientEventSourceProps() { Type sqlClientEventSourceType = Assembly.GetAssembly(typeof(SqlConnection))!.GetType("Microsoft.Data.SqlClient.SqlClientEventSource"); Debug.Assert(sqlClientEventSourceType != null); - FieldInfo logField = sqlClientEventSourceType.GetField("Log", BindingFlags.Static | BindingFlags.NonPublic); - Debug.Assert(logField != null); - s_log = logField.GetValue(null); - - BindingFlags _bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic; - _activeHardConnectionsCounter = - sqlClientEventSourceType.GetField(nameof(_activeHardConnectionsCounter), _bindingFlags); - Debug.Assert(_activeHardConnectionsCounter != null); - _hardConnectsCounter = - sqlClientEventSourceType.GetField(nameof(_hardConnectsCounter), _bindingFlags); - Debug.Assert(_hardConnectsCounter != null); - _hardDisconnectsCounter = - sqlClientEventSourceType.GetField(nameof(_hardDisconnectsCounter), _bindingFlags); - Debug.Assert(_hardDisconnectsCounter != null); - _activeSoftConnectionsCounter = - sqlClientEventSourceType.GetField(nameof(_activeSoftConnectionsCounter), _bindingFlags); - Debug.Assert(_activeSoftConnectionsCounter != null); - _softConnectsCounter = - sqlClientEventSourceType.GetField(nameof(_softConnectsCounter), _bindingFlags); - Debug.Assert(_softConnectsCounter != null); - _softDisconnectsCounter = - sqlClientEventSourceType.GetField(nameof(_softDisconnectsCounter), _bindingFlags); - Debug.Assert(_softDisconnectsCounter != null); - _nonPooledConnectionsCounter = - sqlClientEventSourceType.GetField(nameof(_nonPooledConnectionsCounter), _bindingFlags); - Debug.Assert(_nonPooledConnectionsCounter != null); - _pooledConnectionsCounter = - sqlClientEventSourceType.GetField(nameof(_pooledConnectionsCounter), _bindingFlags); - Debug.Assert(_pooledConnectionsCounter != null); - _activeConnectionPoolGroupsCounter = - sqlClientEventSourceType.GetField(nameof(_activeConnectionPoolGroupsCounter), _bindingFlags); - Debug.Assert(_activeConnectionPoolGroupsCounter != null); - _inactiveConnectionPoolGroupsCounter = - sqlClientEventSourceType.GetField(nameof(_inactiveConnectionPoolGroupsCounter), _bindingFlags); - Debug.Assert(_inactiveConnectionPoolGroupsCounter != null); - _activeConnectionPoolsCounter = - sqlClientEventSourceType.GetField(nameof(_activeConnectionPoolsCounter), _bindingFlags); - Debug.Assert(_activeConnectionPoolsCounter != null); - _inactiveConnectionPoolsCounter = - sqlClientEventSourceType.GetField(nameof(_inactiveConnectionPoolsCounter), _bindingFlags); - Debug.Assert(_inactiveConnectionPoolsCounter != null); - _activeConnectionsCounter = - sqlClientEventSourceType.GetField(nameof(_activeConnectionsCounter), _bindingFlags); - Debug.Assert(_activeConnectionsCounter != null); - _freeConnectionsCounter = - sqlClientEventSourceType.GetField(nameof(_freeConnectionsCounter), _bindingFlags); - Debug.Assert(_freeConnectionsCounter != null); - _stasisConnectionsCounter = - sqlClientEventSourceType.GetField(nameof(_stasisConnectionsCounter), _bindingFlags); - Debug.Assert(_stasisConnectionsCounter != null); - _reclaimedConnectionsCounter = - sqlClientEventSourceType.GetField(nameof(_reclaimedConnectionsCounter), _bindingFlags); - Debug.Assert(_reclaimedConnectionsCounter != null); + FieldInfo metricsField = sqlClientEventSourceType.GetField("Metrics", BindingFlags.Static | BindingFlags.Public); + Debug.Assert(metricsField != null); + Type sqlClientMetricsType = metricsField.FieldType; + s_log = metricsField.GetValue(null); + +#if NETFRAMEWORK + Func notApplicableFunction = static () => -1; + + // .NET Framework doesn't have performance counters for the number of hard and soft connections. + s_getActiveHardConnections = notApplicableFunction; + s_getActiveSoftConnections = notApplicableFunction; +#endif + s_getActiveHardConnections = GenerateFieldGetter("_activeHardConnections"); + s_getHardConnects = GenerateFieldGetter("_hardConnectsRate"); + s_getHardDisconnects = GenerateFieldGetter("_hardDisconnectsRate"); + s_getActiveSoftConnections = GenerateFieldGetter("_activeSoftConnections"); + s_getSoftConnects = GenerateFieldGetter("_softConnectsRate"); + s_getSoftDisconnects = GenerateFieldGetter("_softDisconnectsRate"); + s_getNonPooledConnections = GenerateFieldGetter("_nonPooledConnections"); + s_getPooledConnections = GenerateFieldGetter("_pooledConnections"); + s_getActiveConnectionPoolGroups = GenerateFieldGetter("_activeConnectionPoolGroups"); + s_getInactiveConnectionPoolGroups = GenerateFieldGetter("_inactiveConnectionPoolGroups"); + s_getActiveConnectionPools = GenerateFieldGetter("_activeConnectionPools"); + s_getInactiveConnectionPools = GenerateFieldGetter("_inactiveConnectionPools"); + s_getActiveConnections = GenerateFieldGetter("_activeConnections"); + s_getFreeConnections = GenerateFieldGetter("_freeConnections"); + s_getStasisConnections = GenerateFieldGetter("_stasisConnections"); + s_getReclaimedConnections = GenerateFieldGetter("_reclaimedConnections"); + +#if NET + static Func GenerateFieldGetter(string fieldName) + { + FieldInfo counterField = s_log.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); + + Debug.Assert(counterField != null); + return () => (long)counterField.GetValue(s_log)!; + } +#else + static Func GenerateFieldGetter(string fieldName) + { + FieldInfo counterField = s_log.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); + Debug.Assert(counterField != null); + + PerformanceCounter counter = counterField?.GetValue(s_log) as PerformanceCounter; + return () => counter is null ? -1 : counter.RawValue; + } +#endif } - public static long ActiveHardConnections => (long)_activeHardConnectionsCounter.GetValue(s_log)!; + public static long ActiveHardConnections => s_getActiveHardConnections(); - public static long HardConnects => (long)_hardConnectsCounter.GetValue(s_log)!; + public static long HardConnects => s_getHardConnects(); - public static long HardDisconnects => (long)_hardDisconnectsCounter.GetValue(s_log)!; + public static long HardDisconnects => s_getHardDisconnects(); - public static long ActiveSoftConnections => (long)_activeSoftConnectionsCounter.GetValue(s_log)!; + public static long ActiveSoftConnections => s_getActiveSoftConnections(); - public static long SoftConnects => (long)_softConnectsCounter.GetValue(s_log)!; + public static long SoftConnects => s_getSoftConnects(); - public static long SoftDisconnects => (long)_softDisconnectsCounter.GetValue(s_log)!; + public static long SoftDisconnects => s_getSoftDisconnects(); - public static long NonPooledConnections => (long)_nonPooledConnectionsCounter.GetValue(s_log)!; + public static long NonPooledConnections => s_getNonPooledConnections(); - public static long PooledConnections => (long)_pooledConnectionsCounter.GetValue(s_log)!; + public static long PooledConnections => s_getPooledConnections(); - public static long ActiveConnectionPoolGroups => (long)_activeConnectionPoolGroupsCounter.GetValue(s_log)!; + public static long ActiveConnectionPoolGroups => s_getActiveConnectionPoolGroups(); - public static long InactiveConnectionPoolGroups => (long)_inactiveConnectionPoolGroupsCounter.GetValue(s_log)!; + public static long InactiveConnectionPoolGroups => s_getInactiveConnectionPoolGroups(); - public static long ActiveConnectionPools => (long)_activeConnectionPoolsCounter.GetValue(s_log)!; + public static long ActiveConnectionPools => s_getActiveConnectionPools(); - public static long InactiveConnectionPools => (long)_inactiveConnectionPoolsCounter.GetValue(s_log)!; + public static long InactiveConnectionPools => s_getInactiveConnectionPools(); - public static long ActiveConnections => (long)_activeConnectionsCounter.GetValue(s_log)!; + public static long ActiveConnections => s_getActiveConnections(); - public static long FreeConnections => (long)_freeConnectionsCounter.GetValue(s_log)!; + public static long FreeConnections => s_getFreeConnections(); - public static long StasisConnections => (long)_stasisConnectionsCounter.GetValue(s_log)!; + public static long StasisConnections => s_getStasisConnections(); - public static long ReclaimedConnections => (long)_reclaimedConnectionsCounter.GetValue(s_log)!; + public static long ReclaimedConnections => s_getReclaimedConnections(); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/TestTdsServer.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/TestTdsServer.cs index a4557d72b6..45a817c46e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/TestTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/TestTdsServer.cs @@ -7,6 +7,7 @@ using System.Net; using System.Net.Sockets; using System.Runtime.CompilerServices; +using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using Microsoft.SqlServer.TDS.EndPoint; using Microsoft.SqlServer.TDS.PreLogin; @@ -31,7 +32,7 @@ public TestTdsServer(QueryEngine engine, TDSServerArguments args) : base(args) public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, [CallerMemberName] string methodName = "", - X509Certificate2 encryptionCertificate = null, TDSPreLoginTokenEncryptionType encryptionType = TDSPreLoginTokenEncryptionType.NotSupported) + X509Certificate2 encryptionCertificate = null, SslProtocols encryptionProtocols = SslProtocols.Tls12, TDSPreLoginTokenEncryptionType encryptionType = TDSPreLoginTokenEncryptionType.NotSupported) { TDSServerArguments args = new TDSServerArguments() { @@ -43,11 +44,8 @@ public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool args.FedAuthRequiredPreLoginOption = SqlServer.TDS.PreLogin.TdsPreLoginFedAuthRequiredOption.FedAuthRequired; } - if (encryptionCertificate != null) - { - args.EncryptionCertificate = encryptionCertificate; - } - + args.EncryptionCertificate = encryptionCertificate; + args.EncryptionProtocols = encryptionProtocols; args.Encryption = encryptionType; TestTdsServer server = engine == null ? new TestTdsServer(args) : new TestTdsServer(engine, args); @@ -55,7 +53,7 @@ public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool server._endpoint = new TDSServerEndPoint(server) { ServerEndPoint = new IPEndPoint(IPAddress.Any, 0) }; server._endpoint.EndpointName = methodName; // The server EventLog should be enabled as it logs the exceptions. - server._endpoint.EventLog = Console.Out; + server._endpoint.EventLog = enableLog ? Console.Out : null; server._endpoint.Start(); int port = server._endpoint.ServerEndPoint.Port; @@ -83,9 +81,9 @@ public static TestTdsServer StartServerWithQueryEngine(QueryEngine engine, bool public static TestTdsServer StartTestServer(bool enableFedAuth = false, bool enableLog = false, int connectionTimeout = DefaultConnectionTimeout, [CallerMemberName] string methodName = "", - X509Certificate2 encryptionCertificate = null, TDSPreLoginTokenEncryptionType encryptionType = TDSPreLoginTokenEncryptionType.NotSupported) + X509Certificate2 encryptionCertificate = null, SslProtocols encryptionProtocols = SslProtocols.Tls12, TDSPreLoginTokenEncryptionType encryptionType = TDSPreLoginTokenEncryptionType.NotSupported) { - return StartServerWithQueryEngine(null, enableFedAuth, enableLog, connectionTimeout, methodName, encryptionCertificate, encryptionType); + return StartServerWithQueryEngine(null, enableFedAuth, enableLog, connectionTimeout, methodName, encryptionCertificate, encryptionProtocols, encryptionType); } public void Dispose() => _endpoint?.Stop(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs new file mode 100644 index 0000000000..40fde20faa --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Xml; +using System.Xml.XPath; +using Xunit; + +namespace Microsoft.Data.SqlClient.ManualTesting.Tests +{ + public class XEventsTracingTest + { + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [InlineData("SELECT @@VERSION", System.Data.CommandType.Text, "sql_statement_starting")] + [InlineData("sp_help", System.Data.CommandType.StoredProcedure, "rpc_starting")] + public void XEventActivityIDConsistentWithTracing(string query, System.Data.CommandType commandType, string xEvent) + { + // This test validates that the activity ID recorded in the client-side trace is passed through to the server, + // where it can be recorded in an XEvent session. This is documented at: + // https://learn.microsoft.com/en-us/sql/relational-databases/native-client/features/accessing-diagnostic-information-in-the-extended-events-log + + using (SqlConnection xEventManagementConnection = new SqlConnection(DataTestUtility.TCPConnectionString)) + using (DataTestUtility.XEventScope xEventSession = new DataTestUtility.XEventScope(xEventManagementConnection, + @"ADD EVENT SQL_STATEMENT_STARTING (ACTION (client_connection_id)), + ADD EVENT RPC_STARTING (ACTION (client_connection_id))", + "ADD TARGET ring_buffer")) + { + Guid connectionId; + HashSet ids; + + using (DataTestUtility.MDSEventListener TraceListener = new()) + using (SqlConnection connection = new(DataTestUtility.TCPConnectionString)) + { + connection.Open(); + connectionId = connection.ClientConnectionId; + + using SqlCommand command = new(query, connection) { CommandType = commandType }; + using SqlDataReader reader = command.ExecuteReader(); + while (reader.Read()) + { + // Flush data + } + + ids = TraceListener.ActivityIDs; + } + + XmlDocument eventList = xEventSession.GetEvents(); + // Get the associated activity ID from the XEvent session. We expect to see the same ID in the trace as well. + string activityId = GetCommandActivityId(query, xEvent, connectionId, eventList); + + Assert.Contains(activityId, ids); + } + } + + private static string GetCommandActivityId(string commandText, string eventName, Guid connectionId, XmlDocument xEvents) + { + XPathNavigator xPathRoot = xEvents.CreateNavigator(); + // The transferred activity ID is attached to the "attach_activity_id_xfer" action within + // the "sql_statement_starting" and the "rpc_starting" events. + XPathNodeIterator statementStartingQuery = xPathRoot.Select( + $"/RingBufferTarget/event[@name='{eventName}'" + + $" and action[@name='client_connection_id']/value='{connectionId.ToString().ToUpper()}'" + + $" and (data[@name='statement']='{commandText}' or data[@name='object_name']='{commandText}')]"); + + Assert.Equal(1, statementStartingQuery.Count); + Assert.True(statementStartingQuery.MoveNext()); + + XPathNavigator activityIdElement = statementStartingQuery.Current.SelectSingleNode("action[@name='attach_activity_id_xfer']/value"); + + Assert.NotNull(activityIdElement); + Assert.NotNull(activityIdElement.Value); + + return activityIdElement.Value; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/BaseRunner.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/BaseRunner.cs index 8ec9be9cf9..62cce36f94 100644 --- a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/BaseRunner.cs +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/BenchmarkRunners/BaseRunner.cs @@ -2,17 +2,14 @@ // 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.IO; -using Newtonsoft.Json; - namespace Microsoft.Data.SqlClient.PerformanceTests { public abstract class BaseRunner { public BaseRunner() { - s_config = JsonConvert.DeserializeObject(File.ReadAllText("runnerconfig.json")); - s_datatypes = JsonConvert.DeserializeObject(File.ReadAllText("datatypes.json")); + s_config = Config.Load(); + s_datatypes = DataTypes.Load(); } internal static Config s_config; diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Config.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Config.cs index 3fe22e45b3..f52fb1b554 100644 --- a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Config.cs +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Config.cs @@ -12,6 +12,28 @@ public class Config public string ConnectionString; public bool UseManagedSniOnWindows; public Benchmarks Benchmarks; + + /// + /// Load the benchmark configuration from a JSON file. + /// + /// If the environment variable "RUNNER_CONFIG" is set, it will be used + /// as the path to the config file. Otherwise, the file + /// "runnerconfig.json" in the current working directory will be used. + /// + /// + /// + /// The Config instance populated from the JSON config file. + /// + /// + /// + /// Thrown if the config file cannot be read or deserialized. + /// + /// + public static Config Load() + { + return Loader.FromJsonFile( + "runnerconfig.json", "RUNNER_CONFIG"); + } } public class Benchmarks diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/DataTypes.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/DataTypes.cs index 3140fd6b95..54b59a1015 100644 --- a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/DataTypes.cs +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/DataTypes.cs @@ -18,6 +18,28 @@ public class DataTypes public MaxLengthBinaryType[] Binary; public MaxLengthValueLengthType[] MaxTypes; public DataType[] Others; + + /// + /// Load the data types configuration from a JSON file. + /// + /// If the environment variable "DATATYPES_CONFIG" is set, it will be + /// used as the path to the config file. Otherwise, the file + /// "datatypes.json" in the current working directory will be used. + /// + /// + /// + /// The DataTypes instance populated from the JSON file. + /// + /// + /// + /// Thrown if the config file cannot be read or deserialized. + /// + /// + public static DataTypes Load() + { + return Loader.FromJsonFile( + "datatypes.json", "DATATYPES_CONFIG"); + } } /// diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Loader.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Loader.cs new file mode 100644 index 0000000000..a452216b66 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Config/Loader.cs @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.IO; +using System.Text.Json; + +namespace Microsoft.Data.SqlClient.PerformanceTests +{ + public static class Loader + { + /// + /// Load a JSON config file into the given type. + /// + /// + /// + /// The type to deserialize the JSON into. + /// + /// + /// + /// The path to the JSON config file. + /// + /// + /// + /// An optional environment variable that, if set, will be used as + /// the config file path, ignoring path. + /// + /// + /// + /// The T instance populated from the JSON config file. + /// + /// + /// + /// Thrown if the config file cannot be read or deserialized. + /// + /// + public static T FromJsonFile( + string path, + string envOverride = null) + where T : class + { + string configFile = + envOverride is null + ? path + : Environment.GetEnvironmentVariable(envOverride) + ?? path; + + T config = null; + Exception error = null; + try + { + using var stream = File.OpenRead(configFile); + config = + JsonSerializer.Deserialize( + stream, + new JsonSerializerOptions + { + IncludeFields = true, + ReadCommentHandling = JsonCommentHandling.Skip + }); + } + catch (Exception ex) + { + error = ex; + } + + if (config is null || error is not null) + { + throw new InvalidOperationException( + $"Failed to load {typeof(T).Name} config from file=" + + $"{configFile}", error); + } + + return config; + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Microsoft.Data.SqlClient.PerformanceTests.csproj b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Microsoft.Data.SqlClient.PerformanceTests.csproj index 8fff3a5cab..24bcaab242 100644 --- a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Microsoft.Data.SqlClient.PerformanceTests.csproj +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Microsoft.Data.SqlClient.PerformanceTests.csproj @@ -3,7 +3,6 @@ Exe PerformanceTests net8.0;net9.0 - false Debug;Release; $(ObjFolder)$(Configuration).$(Platform).$(AssemblyName) $(BinFolder)$(Configuration).$(Platform).$(AssemblyName) @@ -16,33 +15,14 @@ - - - + - - - - - - - - - - - - - - - - - diff --git a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Program.cs b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Program.cs index 2b98162de9..8df9d36a3e 100644 --- a/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Program.cs +++ b/src/Microsoft.Data.SqlClient/tests/PerformanceTests/Program.cs @@ -2,22 +2,20 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using BenchmarkDotNet.Running; -using Newtonsoft.Json; using System; -using System.IO; +using BenchmarkDotNet.Running; namespace Microsoft.Data.SqlClient.PerformanceTests { public class Program { - private static Config s_config; + private readonly Config _config; - public static void Main() + public Program() { // Load config file - s_config = JsonConvert.DeserializeObject(File.ReadAllText("runnerconfig.json")); - if (s_config.UseManagedSniOnWindows) + _config = Config.Load(); + if (_config.UseManagedSniOnWindows) { AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", true); } @@ -37,44 +35,50 @@ public static void Main() // Always Encrypted } - private static void Run_SqlConnectionBenchmark() + private void Run_SqlConnectionBenchmark() { - if (s_config.Benchmarks.SqlConnectionRunnerConfig?.Enabled == true) + if (_config.Benchmarks.SqlConnectionRunnerConfig?.Enabled == true) { - BenchmarkRunner.Run(BenchmarkConfig.s_instance(s_config.Benchmarks.SqlConnectionRunnerConfig)); + BenchmarkRunner.Run(BenchmarkConfig.s_instance(_config.Benchmarks.SqlConnectionRunnerConfig)); } } - private static void Run_SqlCommandBenchmark() + private void Run_SqlCommandBenchmark() { - if (s_config.Benchmarks.SqlCommandRunnerConfig?.Enabled == true) + if (_config.Benchmarks.SqlCommandRunnerConfig?.Enabled == true) { - BenchmarkRunner.Run(BenchmarkConfig.s_instance(s_config.Benchmarks.SqlCommandRunnerConfig)); + BenchmarkRunner.Run(BenchmarkConfig.s_instance(_config.Benchmarks.SqlCommandRunnerConfig)); } } - private static void Run_DataTypeReaderBenchmark() + private void Run_DataTypeReaderBenchmark() { - if (s_config.Benchmarks.DataTypeReaderRunnerConfig?.Enabled == true) + if (_config.Benchmarks.DataTypeReaderRunnerConfig?.Enabled == true) { - BenchmarkRunner.Run(BenchmarkConfig.s_instance(s_config.Benchmarks.DataTypeReaderRunnerConfig)); + BenchmarkRunner.Run(BenchmarkConfig.s_instance(_config.Benchmarks.DataTypeReaderRunnerConfig)); } } - private static void Run_DataTypeReaderAsyncBenchmark() + private void Run_DataTypeReaderAsyncBenchmark() { - if (s_config.Benchmarks.DataTypeReaderAsyncRunnerConfig?.Enabled == true) + if (_config.Benchmarks.DataTypeReaderAsyncRunnerConfig?.Enabled == true) { - BenchmarkRunner.Run(BenchmarkConfig.s_instance(s_config.Benchmarks.DataTypeReaderAsyncRunnerConfig)); + BenchmarkRunner.Run(BenchmarkConfig.s_instance(_config.Benchmarks.DataTypeReaderAsyncRunnerConfig)); } } - private static void Run_SqlBulkCopyBenchmark() + private void Run_SqlBulkCopyBenchmark() { - if (s_config.Benchmarks.SqlBulkCopyRunnerConfig?.Enabled == true) + if (_config.Benchmarks.SqlBulkCopyRunnerConfig?.Enabled == true) { - BenchmarkRunner.Run(BenchmarkConfig.s_instance(s_config.Benchmarks.SqlBulkCopyRunnerConfig)); + BenchmarkRunner.Run(BenchmarkConfig.s_instance(_config.Benchmarks.SqlBulkCopyRunnerConfig)); } } + + public static void Main() + { + // Run the benchmarks. + new Program(); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/InternalsVisibleToTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/InternalsVisibleToTest.cs new file mode 100644 index 0000000000..eb924990db --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/InternalsVisibleToTest.cs @@ -0,0 +1,17 @@ +using System; +using Microsoft.Data.SqlClient.ConnectionPool; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests +{ + public class InternalsVisibleToTest + { + [Fact] + public void TestInternalsVisible() + { + DbConnectionPoolAuthenticationContext context = new([1, 2, 3], DateTime.UtcNow.AddMinutes(5)); + Assert.NotNull(context); + Assert.Equal([1, 2, 3], context.AccessToken); + } + } +} 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 new file mode 100644 index 0000000000..2f0e12c922 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft.Data.SqlClient.UnitTests.csproj @@ -0,0 +1,51 @@ + + + UnitTests + netfx + netcoreapp + win + $(ObjFolder)$(Configuration).$(Platform).$(AssemblyName) + $(BinFolder)$(Configuration).$(Platform).$(AssemblyName) + true + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + PreserveNewest + %(Filename)%(Extension) + + + + + PreserveNewest + xunit.runner.json + + + diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPoolTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPoolTest.cs new file mode 100644 index 0000000000..2dcfe476fe --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/ConnectionPool/ChannelDbConnectionPoolTest.cs @@ -0,0 +1,155 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Data.Common; +using System.Threading.Tasks; +using System.Transactions; +using Microsoft.Data.SqlClient.ConnectionPool; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests +{ + public class ChannelDbConnectionPoolTest + { + private readonly ChannelDbConnectionPool _pool; + + public ChannelDbConnectionPoolTest() + { + _pool = new ChannelDbConnectionPool(); + } + + [Fact] + public void TestAuthenticationContexts() + { + Assert.Throws(() => _ = _pool.AuthenticationContexts); + } + + [Fact] + public void TestConnectionFactory() + { + Assert.Throws(() => _ = _pool.ConnectionFactory); + } + + [Fact] + public void TestCount() + { + Assert.Throws(() => _ = _pool.Count); + } + + [Fact] + public void TestErrorOccurred() + { + Assert.Throws(() => _ = _pool.ErrorOccurred); + } + + [Fact] + public void TestId() + { + Assert.Throws(() => _ = _pool.Id); + } + + [Fact] + public void TestIdentity() + { + Assert.Throws(() => _ = _pool.Identity); + } + + [Fact] + public void TestIsRunning() + { + Assert.Throws(() => _ = _pool.IsRunning); + } + + [Fact] + public void TestLoadBalanceTimeout() + { + Assert.Throws(() => _ = _pool.LoadBalanceTimeout); + } + + [Fact] + public void TestPoolGroup() + { + Assert.Throws(() => _ = _pool.PoolGroup); + } + + [Fact] + public void TestPoolGroupOptions() + { + Assert.Throws(() => _ = _pool.PoolGroupOptions); + } + + [Fact] + public void TestProviderInfo() + { + Assert.Throws(() => _ = _pool.ProviderInfo); + } + + [Fact] + public void TestStateGetter() + { + Assert.Throws(() => _ = _pool.State); + } + + [Fact] + public void TestStateSetter() + { + Assert.Throws(() => _pool.State = DbConnectionPoolState.Running); + } + + [Fact] + public void TestUseLoadBalancing() + { + Assert.Throws(() => _ = _pool.UseLoadBalancing); + } + + [Fact] + public void TestClear() + { + Assert.Throws(() => _pool.Clear()); + } + + [Fact] + public void TestPutObjectFromTransactedPool() + { + Assert.Throws(() => _pool.PutObjectFromTransactedPool(null!)); + } + + [Fact] + public void TestReplaceConnection() + { + Assert.Throws(() => _pool.ReplaceConnection(null!, null!, null!)); + } + + [Fact] + public void TestReturnInternalConnection() + { + Assert.Throws(() => _pool.ReturnInternalConnection(null!, null!)); + } + + [Fact] + public void TestShutdown() + { + Assert.Throws(() => _pool.Shutdown()); + } + + [Fact] + public void TestStartup() + { + Assert.Throws(() => _pool.Startup()); + } + + [Fact] + public void TestTransactionEnded() + { + Assert.Throws(() => _pool.TransactionEnded(null!, null!)); + } + + [Fact] + public void TestTryGetConnection() + { + Assert.Throws(() => _pool.TryGetConnection(null!, null!, null!, out _)); + } + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlJsonTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlJsonTest.cs new file mode 100644 index 0000000000..faf44a4e29 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlJsonTest.cs @@ -0,0 +1,157 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Data.SqlTypes; +using System.Text.Json; +using Microsoft.Data.SqlTypes; +using Xunit; + +namespace Microsoft.Data.SqlClient.UnitTests; + +public class SqlJsonTest +{ + #region Private Fields + + private static readonly JsonSerializerOptions _jsonOptions = new() + { + WriteIndented = true + }; + + #endregion + + #region Tests + + // Test the static Null property. + [Fact] + public void StaticNull() + { + Assert.True(SqlJson.Null.IsNull); + Assert.Throws(() => SqlJson.Null.Value); + Assert.Null(SqlJson.Null.ToString()); + } + + // Test the constructor that takes no arguments. + [Fact] + public void Constructor_NoArgs() + { + SqlJson json = new(); + Assert.True(json.IsNull); + Assert.Throws(() => json.Value); + Assert.Null(json.ToString()); + } + + // Test the constructors that take a nullable string. + [Fact] + public void Constructor_String_Null() + { + const string? value = null; + SqlJson json = new(value); + Assert.True(json.IsNull); + Assert.Throws(() => json.Value); + Assert.Null(json.ToString()); + } + + [Fact] + public void Constructor_String_NotNull() + { + const string value = "{\"key\":\"value\"}"; + SqlJson json = new(value); + Assert.False(json.IsNull); + Assert.Equal(value, json.Value); + Assert.Equal(value, json.ToString()); + } + + [Theory] + // Non-string key. + [InlineData("{key:\"value\"}")] + // Invalid value type. + [InlineData("{\"key\":value}")] + // Missing closing brace. + [InlineData("{\"key\":\"value\"")] + // Trailing comma. + [InlineData("{\"key\":\"value\",}")] + // Comment in JSON. + [InlineData("// comment {\"key\":\"value\"}")] + public void Constructor_String_Invalid(string invalid) + { + Assert.ThrowsAny(() => new SqlJson(invalid)); + } + + // Test the constructor that takes a nullable JsonDocument. + [Fact] + public void Constructor_JsonDocument_Null() + { + const JsonDocument? doc = null; + SqlJson json = new(doc); + Assert.True(json.IsNull); + Assert.Throws(() => json.Value); + Assert.Null(json.ToString()); + } + + [Fact] + public void Constructor_JsonDocument_NotNull() + { + using JsonDocument doc = GenerateRandomJson(); + SqlJson json = new(doc); + Assert.False(json.IsNull); + Assert.Equal(doc.RootElement.GetRawText(), json.Value); + Assert.Equal(doc.RootElement.GetRawText(), json.ToString()); + } + + [Fact] + public void Constructor_JsonDocument_Disposed() + { + using JsonDocument doc = GenerateRandomJson(); + doc.Dispose(); + Assert.Throws(() => new SqlJson(doc)); + } + + // IsNull, Value, and ToString() are covered by the above tests. + + // Test that the Value can be round-tripped through a JsonDocument. + // + // JsonElement.DeepEquals() is only available in .NET 9.0 and later. + #if NET9_0_OR_GREATER + [Fact] + public void RoundTrip() + { + using JsonDocument doc = GenerateRandomJson(); + SqlJson json = new(doc); + + using var outputDocument = JsonDocument.Parse(json.Value); + Assert.True(JsonElement.DeepEquals( + doc.RootElement, outputDocument.RootElement)); + } + #endif + + #endregion + + #region Helpers + + private static JsonDocument GenerateRandomJson() + { + Random random = new(); + + object jsonObject = new + { + id = random.Next(1, 1000), + name = $"Name{random.Next(1, 100)}", + isActive = random.Next(0, 2) == 1, + createdDate = DateTime.Now.AddDays(-random.Next(1, 100)).ToString("yyyy-MM-ddTHH:mm:ssZ"), + scores = new int[] { random.Next(1, 100), random.Next(1, 100), random.Next(1, 100) }, + details = new + { + age = random.Next(18, 60), + city = $"City{random.Next(1, 100)}" + } + }; + + return JsonSerializer.SerializeToDocument(jsonObject, _jsonOptions); + } + + #endregion +} 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 new file mode 100644 index 0000000000..ae7b1d9901 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlTypes/SqlTypeWorkaroundsTests.cs @@ -0,0 +1,207 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Data.SqlTypes; +using Microsoft.Data.SqlTypes; +using Xunit; + +#nullable enable + +namespace Microsoft.Data.SqlClient.UnitTests +{ + public class SqlTypeWorkaroundsTests + { + // @TODO: Need a facade pattern for Type getting so we can test the case where reflection fails + + #if NETFRAMEWORK + + #region SqlBinary + + public static TheoryData ByteBinaryCtor_NonNullInput_Data => + new TheoryData + { + Array.Empty(), + new byte[] { 1, 2, 3, 4}, + }; + + [Theory] + [MemberData(nameof(ByteBinaryCtor_NonNullInput_Data))] + public void SqlBinaryCtor_NonNullInput(byte[] input) + { + // Act + SqlBinary result = SqlTypeWorkarounds.SqlBinaryCtor(input, true); + + // Assert + Assert.False(result.IsNull); + Assert.Equal(input, result.Value); + } + + [Fact] + public void SqlBinaryCtor_NullInput() + { + // Act + SqlBinary result = SqlTypeWorkarounds.SqlBinaryCtor(null, true); + + // Assert + Assert.True(result.IsNull); + } + + #endregion + + #region SqlDecimal + + public static TheoryData SqlDecimalExtractData_NonNullInput_Data => + new TheoryData + { + SqlDecimal.MinValue, + new SqlDecimal(-1.2345678), + new SqlDecimal(0), + new SqlDecimal(1.2345678), + SqlDecimal.MaxValue, + }; + + [Theory] + [MemberData(nameof(SqlDecimalExtractData_NonNullInput_Data))] + public void SqlDecimalExtractData_NonNullInput(SqlDecimal input) + { + // Act + SqlTypeWorkarounds.SqlDecimalExtractData( + input, + out uint data1, + out uint data2, + out uint data3, + out uint data4); + + // Assert + int[] expected = input.Data; + Assert.Equal(expected[0], (int)data1); + Assert.Equal(expected[1], (int)data2); + Assert.Equal(expected[2], (int)data3); + Assert.Equal(expected[3], (int)data4); + } + + [Fact] + public void SqlDecimalExtractData_NullInput() + { + Action action = () => + { + // Arrange + SqlDecimal input = SqlDecimal.Null; + + // Act + SqlTypeWorkarounds.SqlDecimalExtractData(input, out _, out _, out _, out _); + }; + + // Assert + Assert.Throws(action); + } + + #endregion + + #region SqlGuid + + public static TheoryData SqlGuidCtor_InvalidInput_Data => + new TheoryData + { + null, + Array.Empty(), + new byte[] { 1, 2, 3, 4 }, // Too short + new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 } // Too long + }; + + [Theory] + [MemberData(nameof(SqlGuidCtor_InvalidInput_Data))] + public void SqlGuidCtor_InvalidInput(byte[]? input) + { + // Act + Action action = () => SqlTypeWorkarounds.SqlGuidCtor(input, true); + + // Assert + Assert.Throws(action); + } + + public static TheoryData SqlGuidCtor_ValidInput_Data => + new TheoryData + { + 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 } + }; + + [Theory] + [MemberData(nameof(SqlGuidCtor_ValidInput_Data))] + public void SqlGuidCtor_ValidInput(byte[] input) + { + // Act + SqlGuid result = SqlTypeWorkarounds.SqlGuidCtor(input, true); + + // Assert + Assert.False(result.IsNull); + Assert.Equal(input, result.Value.ToByteArray()); + } + + #endregion + + #region SqlMoney + + public static TheoryData SqlMoneyCtor_Data => + new TheoryData + { + { long.MinValue, SqlMoney.MinValue }, + { (long)((decimal)-123000000 / 10000), new SqlMoney(-1.23) }, + { 0, SqlMoney.Zero }, + { (long)((decimal)123000000 / 10000), new SqlMoney(1.23) }, + { long.MaxValue, SqlMoney.MaxValue }, + }; + + [Theory] + [MemberData(nameof(SqlMoneyCtor_Data))] + public void SqlMoneyCtor(long input, SqlMoney expected) + { + // Act + SqlMoney result = SqlTypeWorkarounds.SqlMoneyCtor(input, 1); + + // Assert + Assert.Equal(expected, result); + } + + public static TheoryData SqlMoneyToSqlInternalRepresentation_NonNullInput_Data => + new TheoryData + { + { SqlMoney.MinValue, long.MinValue }, + { new SqlMoney(-1.23), (long)(new SqlMoney(-1.23).ToDecimal() * 10000) }, + { SqlMoney.Zero, 0 }, + { new SqlMoney(1.23), (long)(new SqlMoney(1.23).ToDecimal() * 10000) }, + { SqlMoney.MaxValue, long.MaxValue }, + }; + + [Theory] + [MemberData(nameof(SqlMoneyToSqlInternalRepresentation_NonNullInput_Data))] + public void SqlMoneyToSqlInternalRepresentation_NonNullInput(SqlMoney input, long expected) + { + // Act + long result = SqlTypeWorkarounds.SqlMoneyToSqlInternalRepresentation(input); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void SqlMoneyToSqlInternalRepresentation_NullInput() + { + // Arrange + SqlMoney input = SqlMoney.Null; + + // Act + Action action = () => SqlTypeWorkarounds.SqlMoneyToSqlInternalRepresentation(input); + + // Assert + Assert.Throws(action); + } + + #endregion + + #endif + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SqlVectorTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SqlVectorTest.cs new file mode 100644 index 0000000000..c3d9869201 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SqlVectorTest.cs @@ -0,0 +1,246 @@ + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.Data.SqlTypes; +using Xunit; + +#nullable enable + +namespace Microsoft.Data.SqlClient.Tests; + +public class SqlVectorTest +{ + #region Tests + + [Fact] + public void UnsupportedType() + { + Assert.Throws(() => SqlVector.CreateNull(5)); + Assert.Throws(() => SqlVector.CreateNull(5)); + Assert.Throws(() => SqlVector.CreateNull(5)); + } + + [Fact] + public void Construct_Length_Negative() + { + Assert.Throws(() => SqlVector.CreateNull(-1)); + } + + [Fact] + public void Construct_Length() + { + var vec = SqlVector.CreateNull(5); + Assert.True(vec.IsNull); + Assert.Equal(5, vec.Length); + // Note that ReadOnlyMemory<> equality checks that both instances point + // to the same memory. We want to check memory content equality, so we + // compare their arrays instead. + Assert.Equal(new ReadOnlyMemory().ToArray(), vec.Memory.ToArray()); + Assert.Equal(SQLResource.NullString, vec.GetString()); + + var ivec = vec as ISqlVector; + Assert.Equal(0x00, ivec.ElementType); + Assert.Equal(0x04, ivec.ElementSize); + Assert.Equal(28, ivec.Size); + Assert.Empty(ivec.VectorPayload); + } + + [Fact] + public void Construct_WithLengthZero() + { + var vec = SqlVector.CreateNull(0); + Assert.True(vec.IsNull); + Assert.Equal(0, vec.Length); + + // Note that ReadOnlyMemory<> equality checks that both instances point + // to the same memory. We want to check memory content equality, so we + // compare their arrays instead. + Assert.Equal(new ReadOnlyMemory().ToArray(), vec.Memory.ToArray()); + Assert.Equal(SQLResource.NullString, vec.GetString()); + + var ivec = vec as ISqlVector; + Assert.Equal(0x00, ivec.ElementType); + Assert.Equal(0x04, ivec.ElementSize); + Assert.Equal(8, ivec.Size); + Assert.Empty(ivec.VectorPayload); + } + + [Fact] + public void Construct_Memory_Empty() + { + SqlVector vec = new(new ReadOnlyMemory()); + Assert.False(vec.IsNull); + Assert.Equal(0, vec.Length); + Assert.Equal(new ReadOnlyMemory().ToArray(), vec.Memory.ToArray()); + Assert.Equal("[]", vec.GetString()); + + var ivec = vec as ISqlVector; + Assert.Equal(0x00, ivec.ElementType); + Assert.Equal(0x04, ivec.ElementSize); + Assert.Equal(8, ivec.Size); + Assert.Equal( + new byte[] { 0xA9, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, + ivec.VectorPayload); + } + + [Fact] + public void Construct_Memory() + { + float[] data = [1.1f, 2.2f]; + ReadOnlyMemory memory = new(data); + SqlVector vec = new(memory); + Assert.False(vec.IsNull); + Assert.Equal(2, vec.Length); + Assert.Equal(memory.ToArray(), vec.Memory.ToArray()); + Assert.Equal(data, vec.Memory.ToArray()); + #if NETFRAMEWORK + Assert.Equal("[1.10000002,2.20000005]", vec.GetString()); + #else + Assert.Equal("[1.1,2.2]", vec.GetString()); + #endif + var ivec = vec as ISqlVector; + Assert.Equal(0x00, ivec.ElementType); + Assert.Equal(0x04, ivec.ElementSize); + Assert.Equal(16, ivec.Size); + Assert.Equal( + MakeTdsPayload( + new byte[] { 0xA9, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 }, + memory), + ivec.VectorPayload); + } + + [Fact] + public void Construct_Memory_ImplicitConversionFromFloatArray() + { + float[] data = new float[] { 3.3f, 4.4f, 5.5f }; + var vec = new SqlVector(data); + Assert.False(vec.IsNull); + Assert.Equal(3, vec.Length); + Assert.Equal(new ReadOnlyMemory(data).ToArray(), vec.Memory.ToArray()); + Assert.Equal(data, vec.Memory.ToArray()); + #if NETFRAMEWORK + Assert.Equal("[3.29999995,4.4000001,5.5]", vec.GetString()); + #else + Assert.Equal("[3.3,4.4,5.5]", vec.GetString()); + #endif + + var ivec = vec as ISqlVector; + Assert.Equal(0x00, ivec.ElementType); + Assert.Equal(0x04, ivec.ElementSize); + Assert.Equal(20, ivec.Size); + Assert.Equal( + MakeTdsPayload( + new byte[] { 0xA9, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }, + data), + ivec.VectorPayload); + } + + [Fact] + public void Construct_Bytes() + { + float[] data = new float[] { 6.6f, 7.7f }; + var bytes = + MakeTdsPayload( + new byte[] { 0xA9, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 }, + data); + + var vec = new SqlVector(bytes); + Assert.False(vec.IsNull); + Assert.Equal(2, vec.Length); + Assert.Equal(new ReadOnlyMemory(data).ToArray(), vec.Memory.ToArray()); + Assert.Equal(data, vec.Memory.ToArray()); + #if NETFRAMEWORK + Assert.Equal("[6.5999999,7.69999981]", vec.GetString()); + #else + Assert.Equal("[6.6,7.7]", vec.GetString()); + #endif + + var ivec = vec as ISqlVector; + Assert.Equal(0x00, ivec.ElementType); + Assert.Equal(0x04, ivec.ElementSize); + Assert.Equal(16, ivec.Size); + Assert.Equal(bytes, ivec.VectorPayload); + } + + [Fact] + public void Construct_Bytes_ShortHeader() + { + Assert.Throws(() => + { + new SqlVector(new byte[] { 0xA9, 0x01, 0x00, 0x00 }); + }); + } + + [Fact] + public void Construct_Bytes_UnknownMagic() + { + Assert.Throws(() => + { + new SqlVector( + new byte[] { 0xA8, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + }); + } + + [Fact] + public void Construct_Bytes_UnsupportedVersion() + { + Assert.Throws(() => + { + new SqlVector( + new byte[] { 0xA9, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }); + }); + } + + [Fact] + public void Construct_Bytes_TypeMismatch() + { + Assert.Throws(() => + { + new SqlVector( + new byte[] { 0xA9, 0x01, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00 }); + }); + } + + [Fact] + public void Construct_Bytes_LengthMismatch() + { + // The header indicates 2 elements, but the payload has 3 floats. + var header = new byte[] { 0xA9, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 }; + var bytes = MakeTdsPayload( + header, + new ReadOnlyMemory(new float[] { 1.1f, 2.2f, 3.3f })); + + Assert.Throws(() => + { + new SqlVector(bytes); + }); + } + + [Fact] + public void Null_Property() + { + Assert.Null(SqlVector.Null); + } + + #endregion + + #region Helpers + + private byte[] MakeTdsPayload(byte[] header, ReadOnlyMemory values) + { + int length = header.Length + (values.Length * sizeof(float)); + byte[] payload = new byte[length]; + header.CopyTo(payload, 0); + for (int i = 0; i < values.Length; i++) + { + var offset = header.Length + (i * sizeof(float)); + BitConverter.GetBytes(values.Span[i]).CopyTo(payload, offset); + } + return payload; + } + + #endregion +} diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities/Microsoft.Data.SqlClient.ExtUtilities.csproj b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities/Microsoft.Data.SqlClient.ExtUtilities.csproj index 7cb8f9a46c..cd3ffeb61f 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities/Microsoft.Data.SqlClient.ExtUtilities.csproj +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.ExtUtilities/Microsoft.Data.SqlClient.ExtUtilities.csproj @@ -1,12 +1,19 @@  Exe - net9.0 Microsoft.Data.SqlClient.ExtUtilities.Runner + net9.0 - - + + + + + + diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Microsoft.Data.SqlClient.TestUtilities.csproj b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Microsoft.Data.SqlClient.TestUtilities.csproj index 3bd48830cc..45ba5c2cf9 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Microsoft.Data.SqlClient.TestUtilities.csproj +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Microsoft.Data.SqlClient.TestUtilities.csproj @@ -1,6 +1,9 @@ - + - netstandard2.0 + netfx + netcoreapp + win + win-$(Platform) $(ObjFolder)$(Configuration).$(Platform)\$(AssemblyName) $(BinFolder)$(Configuration).$(Platform)\$(AssemblyName) @@ -11,6 +14,10 @@ PreserveNewest - + + + + + - \ No newline at end of file + diff --git a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json index d159b7628d..a5f6cd996e 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json +++ b/src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/config.default.json @@ -1,6 +1,13 @@ { + // Typical Windows SQL Server on the local host via TCP. "TCPConnectionString": "Data Source=tcp:localhost;Database=Northwind;Integrated Security=true;Encrypt=false;", + + // Typical Linux SQL Server on the local host, WSL, or a container, via TCP. + // "TCPConnectionString": "Data Source=tcp:127.0.0.1;User Id=sa;Password=;Database=Northwind;Encrypt=false;TrustServerCertificate=true", + + // Typical Windows SQL Server on the local host via Named Pipes. "NPConnectionString": "Data Source=np:localhost;Database=Northwind;Integrated Security=true;Encrypt=false;", + "TCPConnectionStringHGSVBS": "", "TCPConnectionStringNoneVBS": "", "TCPConnectionStringAASSGX": "", @@ -28,5 +35,6 @@ "ManagedIdentitySupported": true, "UserManagedIdentityClientId": "", "PowerShellPath": "", - "AliasName": "" + "AliasName": "", + "IsJsonSupported": false } diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/ITDSServerSession.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/ITDSServerSession.cs index bbe039d426..9b5b7804b4 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/ITDSServerSession.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/ITDSServerSession.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using Microsoft.SqlServer.TDS.EndPoint.SSPI; @@ -68,6 +69,11 @@ public interface ITDSServerSession /// X509Certificate EncryptionCertificate { get; } + /// + /// SSL/TLS protocols to use for transport encryption + /// + SslProtocols EncryptionProtocols { get; } + /// /// Counter of connection reset requests for this session /// @@ -82,5 +88,10 @@ public interface ITDSServerSession /// Indicates whether the client supports Json column type /// bool IsJsonSupportEnabled { get; set; } + + /// + /// Indicates whether the client supports Vector column type + /// + bool IsVectorSupportEnabled { get; set; } } } diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDS.EndPoint.csproj b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDS.EndPoint.csproj index d4b5fe3346..cae90f7473 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDS.EndPoint.csproj +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDS.EndPoint.csproj @@ -53,6 +53,6 @@ - + diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSParser.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSParser.cs index 2027c7bd7f..a502ae24ba 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSParser.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSParser.cs @@ -25,11 +25,6 @@ public class TDSParser /// public TextWriter EventLog { get; set; } - /// - /// Encryption protocol for server to use with AuthenticateAsServer - /// - public static SslProtocols ServerSslProtocol { get; set; } - /// /// Protocol stream between the client and the server /// @@ -43,8 +38,6 @@ public TDSParser(Stream transport) // Save original transport _originalTransport = transport; - ServerSslProtocol = SslProtocols.Tls12; - // Wrap transport layer with TDS Transport = new TDSStream(transport, false); } @@ -57,14 +50,6 @@ public void SetTDSStreamPreWriteCallback(Func funcTDSS Transport.PreWriteCallBack = funcTDSStreamPreWriteCallBack; } - /// - /// Resets the targeted encryption protocol for the server. - /// - public static void ResetTargetProtocol() - { - ServerSslProtocol = SslProtocols.Tls12; - } - /// /// Enable transport encryption /// @@ -105,7 +90,7 @@ protected void EnableClientTransportEncryption(string server) /// /// Enable transport encryption /// - protected void EnableServerTransportEncryption(X509Certificate certificate) + protected void EnableServerTransportEncryption(X509Certificate certificate, SslProtocols encryptionProtocols) { // Check if transport encryption is applied if (Transport.InnerStream is SslStream) @@ -113,6 +98,12 @@ protected void EnableServerTransportEncryption(X509Certificate certificate) return; } + // The SSL certificate is required for the server to handle transport encryption + if (certificate == null) + { + throw new AuthenticationException("Server is unable to authenticate transport encryption without a valid SSL certificate."); + } + Log("Enabling server transport encryption..."); // Wrap TDS stream with auto TDS stream @@ -128,7 +119,7 @@ protected void EnableServerTransportEncryption(X509Certificate certificate) SslStream ssl = new SslStream(multiplexer, true); // Secure the channel - ssl.AuthenticateAsServer(certificate, false, ServerSslProtocol, false); + ssl.AuthenticateAsServer(certificate, false, encryptionProtocols, false); // Replace TDS stream with raw transport stream in multiplexer multiplexer.InnerStream = Transport.InnerStream; diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSServerParser.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSServerParser.cs index 019fefd907..05078d7e96 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSServerParser.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDSServerParser.cs @@ -146,7 +146,7 @@ public void Run() if (Session.Encryption == TDSEncryptionType.LoginOnly || Session.Encryption == TDSEncryptionType.Full) { // Enable server side encryption - EnableServerTransportEncryption(Session.EncryptionCertificate); + EnableServerTransportEncryption(Session.EncryptionCertificate, Session.EncryptionProtocols); } } diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServer.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServer.cs index 06cc2b7401..ac04fd2f57 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServer.cs @@ -27,6 +27,43 @@ namespace Microsoft.SqlServer.TDS.Servers /// public class GenericTDSServer : ITDSServer { + /// + /// Delegate to be called when a LOGIN7 request has been received and is + /// validated. This is called before any authentication work is done, + /// and before any response is sent. + /// + public delegate void OnLogin7ValidatedDelegate( + TDSLogin7Token login7Token); + public OnLogin7ValidatedDelegate OnLogin7Validated { private get; set; } + + /// + /// Delegate to be called when authentication is completed and TDSResponse + /// message is sent to the client. + /// + public delegate void OnAuthenticationCompletedDelegate( + TDSMessage response); + public OnAuthenticationCompletedDelegate OnAuthenticationResponseCompleted { private get; set; } + + /// + /// Default feature extension version supported on the server for vector support. + /// + public const byte DefaultSupportedVectorFeatureExtVersion = 0x01; + + /// + /// Property for setting server version for vector feature extension. + /// + public bool EnableVectorFeatureExt { get; set; } = false; + + /// + /// Property for setting server version for vector feature extension. + /// + public byte ServerSupportedVectorFeatureExtVersion { get; set; } = DefaultSupportedVectorFeatureExtVersion; + + /// + /// Client version for vector FeatureExtension. + /// + private byte _clientSupportedVectorFeatureExtVersion = 0; + /// /// Session counter /// @@ -85,8 +122,9 @@ public virtual ITDSServerSession OpenSession() // Create a new session GenericTDSServerSession session = new GenericTDSServerSession(this, (uint)_sessionCount); - // Use configured encryption certificate + // Use configured encryption certificate and protocols session.EncryptionCertificate = Arguments.EncryptionCertificate; + session.EncryptionProtocols = Arguments.EncryptionProtocols; return session; } @@ -238,6 +276,18 @@ public virtual TDSMessageCollection OnLogin7Request(ITDSServerSession session, T session.IsJsonSupportEnabled = true; break; } + + case TDSFeatureID.VectorSupport: + { + if (EnableVectorFeatureExt) + { + // Enable Vector Support + session.IsVectorSupportEnabled = true; + _clientSupportedVectorFeatureExtVersion = ((TDSLogin7GenericOptionToken)option).Data[0]; + } + break; + } + default: { // Do nothing @@ -247,6 +297,8 @@ public virtual TDSMessageCollection OnLogin7Request(ITDSServerSession session, T } } + OnLogin7Validated?.Invoke(loginRequest); + // Check if SSPI authentication is requested if (loginRequest.OptionalFlags2.IntegratedSecurity == TDSLogin7OptionalFlags2IntSecurity.On) { @@ -576,6 +628,32 @@ protected virtual TDSMessageCollection OnAuthenticationCompleted(ITDSServerSessi } } + // Check if Vector is supported + if (session.IsVectorSupportEnabled) + { + // Create ack data (1 byte: Version number) + byte[] data = new byte[1]; + data[0] = ServerSupportedVectorFeatureExtVersion > _clientSupportedVectorFeatureExtVersion ? _clientSupportedVectorFeatureExtVersion : ServerSupportedVectorFeatureExtVersion; + + // Create vector support as a generic feature extension option + TDSFeatureExtAckGenericOption vectorSupportOption = new TDSFeatureExtAckGenericOption(TDSFeatureID.VectorSupport, (uint)data.Length, data); + + // Look for feature extension token + TDSFeatureExtAckToken featureExtAckToken = (TDSFeatureExtAckToken)responseMessage.Where(t => t is TDSFeatureExtAckToken).FirstOrDefault(); + + if (featureExtAckToken == null) + { + // Create feature extension ack token + featureExtAckToken = new TDSFeatureExtAckToken(vectorSupportOption); + responseMessage.Add(featureExtAckToken); + } + else + { + // Update the existing token + featureExtAckToken.Options.Add(vectorSupportOption); + } + } + // Create DONE token TDSDoneToken doneToken = new TDSDoneToken(TDSDoneTokenStatusType.Final); @@ -585,6 +663,9 @@ protected virtual TDSMessageCollection OnAuthenticationCompleted(ITDSServerSessi // Serialize DONE token into the response packet responseMessage.Add(doneToken); + // Invoke delegate for response validation + OnAuthenticationResponseCompleted?.Invoke(responseMessage); + // Wrap a single message in a collection return new TDSMessageCollection(responseMessage); } diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServerSession.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServerSession.cs index 3272036e15..e9e65d5f8f 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServerSession.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/GenericTDSServerSession.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using Microsoft.SqlServer.TDS.EndPoint; using Microsoft.SqlServer.TDS.EndPoint.SSPI; @@ -78,6 +79,11 @@ public class GenericTDSServerSession : ITDSServerSession /// public X509Certificate EncryptionCertificate { get; set; } + /// + /// SSL/TLS protocols to use for transport encryption + /// + public SslProtocols EncryptionProtocols { get; set; } + /// /// Nonce option sent by client /// @@ -113,6 +119,11 @@ public class GenericTDSServerSession : ITDSServerSession /// public bool IsJsonSupportEnabled { get; set; } + /// + /// Indicates whether this session supports Vector column type + /// + public bool IsVectorSupportEnabled { get; set; } + #region Session Options /// diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TDS.Servers.csproj b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TDS.Servers.csproj index cb89f362ea..b7757b257b 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TDS.Servers.csproj +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TDS.Servers.csproj @@ -26,9 +26,6 @@ - - Always - diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TDSServerArguments.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TDSServerArguments.cs index 1543ebde63..88e577ab68 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TDSServerArguments.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TDSServerArguments.cs @@ -4,6 +4,7 @@ using System; using System.IO; +using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; using Microsoft.SqlServer.TDS.PreLogin; @@ -69,6 +70,11 @@ public class TDSServerArguments /// public X509Certificate EncryptionCertificate { get; set; } + /// + /// SSL/TLS protocols to use for transport encryption + /// + public SslProtocols EncryptionProtocols { get; set; } + /// /// Initialization constructor /// @@ -87,7 +93,8 @@ public TDSServerArguments() // By Default SQL authentication will be used. FedAuthRequiredPreLoginOption = TdsPreLoginFedAuthRequiredOption.FedAuthNotRequired; - EncryptionCertificate = new X509Certificate2("TdsServerCertificate.pfx", "SecretPassword123456"); + EncryptionCertificate = null; + EncryptionProtocols = SslProtocols.Tls12; ServerPrincipalName = AzureADServicePrincipalName; StsUrl = AzureADProductionTokenEndpoint; diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TdsServerCertificate.pfx b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TdsServerCertificate.pfx deleted file mode 100644 index a5c48e2aee..0000000000 Binary files a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TdsServerCertificate.pfx and /dev/null differ diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientFaultTDSServer.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientFaultTDSServer.cs index eda4de8e2a..1933444df6 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientFaultTDSServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.Servers/TransientFaultTDSServer.cs @@ -132,7 +132,7 @@ public static TransientFaultTDSServer StartServerWithQueryEngine(QueryEngine eng server._endpoint.EndpointName = methodName; // The server EventLog should be enabled as it logs the exceptions. - server._endpoint.EventLog = Console.Out; + server._endpoint.EventLog = enableLog ? Console.Out : null; server._endpoint.Start(); server.Port = server._endpoint.ServerEndPoint.Port; diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSFeatureID.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSFeatureID.cs index eb84a631d0..6bb6fbc8d2 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSFeatureID.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSFeatureID.cs @@ -24,6 +24,11 @@ public enum TDSFeatureID : byte /// JsonSupport = 0x0D, + /// + /// Vector Support + /// + VectorSupport = 0x0E, + /// /// End of the list /// diff --git a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSVersion.cs b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSVersion.cs index 0f27264406..d17d879ed4 100644 --- a/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSVersion.cs +++ b/src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDSVersion.cs @@ -11,6 +11,16 @@ namespace Microsoft.SqlServer.TDS /// public static class TDSVersion { + /// + /// 7.0 (Sphinx) TDS version + /// + public static Version SqlServer7_0 = new Version(7, 0, 0, 0); + + /// + /// 2000 (Shiloh) TDS version + /// + public static Version SqlServer2000 = new Version(7, 1, 0, 1); + /// /// 2005 (Yukon) TDS version /// @@ -46,6 +56,14 @@ public static Version GetTDSVersion(Version buildVersion) { return SqlServer2005; } + else if (buildVersion.Major == 8) + { + return SqlServer2000; + } + else if (buildVersion.Major == 7) + { + return SqlServer7_0; + } else { // Not supported TDS version @@ -79,7 +97,7 @@ public static Version Resolve(Version tdsServer, Version tdsClient) /// public static bool IsSupported(Version tdsVersion) { - return tdsVersion >= SqlServer2005 && tdsVersion <= SqlServer2012; + return tdsVersion >= SqlServer7_0 && tdsVersion <= SqlServer2012; } } } diff --git a/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj b/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj index 2808094a25..48d163cab6 100644 --- a/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj +++ b/src/Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj @@ -16,11 +16,11 @@ - + - + diff --git a/src/docker-compose.dcproj b/src/docker-compose.dcproj deleted file mode 100644 index 2bbd2d6762..0000000000 --- a/src/docker-compose.dcproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - 2.1 - Linux - f5df2fdc-c860-4cb3-8b24-7c903c6fc076 - - - microsoft.data.sqlclient.dockertests - - - - - - \ No newline at end of file diff --git a/src/docker-compose.yml b/src/docker-compose.yml deleted file mode 100644 index 6b57fa25dd..0000000000 --- a/src/docker-compose.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: '3.9' - -services: - microsoft.data.sqlclient.dockertests: - image: ${DOCKER_REGISTRY-}microsoftdatasqlclientdockerlinuxtest - build: - context: ../ - dockerfile: src/Microsoft.Data.SqlClient/tests/Docker/DockerLinuxTest/Dockerfile - depends_on: - - microsoft.sqlserver - - microsoft.sqlserver: - image: mcr.microsoft.com/mssql/server:2019-latest - environment: - - SA_PASSWORD=P@ssw0rd!123 - - ACCEPT_EULA=Y - ports: - - "5434:1433" diff --git a/tools/Directory.Packages.props b/tools/Directory.Packages.props new file mode 100644 index 0000000000..e0f9ef7b88 --- /dev/null +++ b/tools/Directory.Packages.props @@ -0,0 +1,18 @@ + + + true + + + + + + + + + + + + + + + diff --git a/tools/GenAPI/Directory.Build.Props b/tools/GenAPI/Directory.Build.Props index f3a5ed8508..8eb4acce29 100644 --- a/tools/GenAPI/Directory.Build.Props +++ b/tools/GenAPI/Directory.Build.Props @@ -5,4 +5,8 @@ + + + false + diff --git a/tools/GenAPI/Microsoft.Cci.Extensions/Microsoft.Cci.Extensions.csproj b/tools/GenAPI/Microsoft.Cci.Extensions/Microsoft.Cci.Extensions.csproj index 299e6c19ab..3c8676acac 100644 --- a/tools/GenAPI/Microsoft.Cci.Extensions/Microsoft.Cci.Extensions.csproj +++ b/tools/GenAPI/Microsoft.Cci.Extensions/Microsoft.Cci.Extensions.csproj @@ -16,13 +16,13 @@ - - - - - - - + + + + + + + diff --git a/tools/GenAPI/Microsoft.DotNet.GenAPI/Microsoft.DotNet.GenAPI.csproj b/tools/GenAPI/Microsoft.DotNet.GenAPI/Microsoft.DotNet.GenAPI.csproj index dd8e477405..1ee92e1808 100644 --- a/tools/GenAPI/Microsoft.DotNet.GenAPI/Microsoft.DotNet.GenAPI.csproj +++ b/tools/GenAPI/Microsoft.DotNet.GenAPI/Microsoft.DotNet.GenAPI.csproj @@ -12,9 +12,10 @@ - - - + + + + diff --git a/tools/GenAPI/Microsoft.DotNet.GenAPI/Program.cs b/tools/GenAPI/Microsoft.DotNet.GenAPI/Program.cs index 0a4617f6be..d2ad61f26c 100644 --- a/tools/GenAPI/Microsoft.DotNet.GenAPI/Program.cs +++ b/tools/GenAPI/Microsoft.DotNet.GenAPI/Program.cs @@ -33,7 +33,7 @@ internal partial class Program private static int Main(string[] args) { - var app = new CommandLineApplication + using var app = new CommandLineApplication { Name = "GenAPI", FullName = "A command line tool to generate code for the API surface of an assembly.", diff --git a/tools/credScan/CredScanSuppressions.json b/tools/credScan/CredScanSuppressions.json index 7b38d855f2..7af0e6a1b5 100644 --- a/tools/credScan/CredScanSuppressions.json +++ b/tools/credScan/CredScanSuppressions.json @@ -20,14 +20,6 @@ { "file": "TDSServerArguments.cs", "_justification": "Contains dummy passwords used for internal testing" - }, - { - "file": "Program.cs", - "_justification": "Contains dummy passwords used for docker testing" - }, - { - "file": "docker-compose.yml", - "_justification": "Contains dummy passwords used for docker testing" } ] } diff --git a/tools/intellisense/TrimDocs.ps1 b/tools/intellisense/TrimDocs.ps1 new file mode 100644 index 0000000000..c5e7eeb893 --- /dev/null +++ b/tools/intellisense/TrimDocs.ps1 @@ -0,0 +1,32 @@ +# Name: TrimDocs.ps1 +# Description: This script removes all and nodes from an XML documentation file. +# Usage: .\TrimDocs.ps1 -inputFile "path\to\input.xml" -outputFile "path\to\output.xml" + +param ( + [string]$inputFile="", + [string]$outputFile="" +) + +# Validate inputFile exists +if (-not (Test-Path $inputFile)) { + Write-Host "XML File not found: $inputFile" + exit +} + +[xml]$xml = Get-Content $inputFile + +# Remove all nodes +$nodes = $xml.SelectNodes("//remarks") +foreach ($node in $nodes) { + $node.ParentNode.RemoveChild($node) | Out-Null +} +# Remove all nodes +$nodes = $xml.SelectNodes("//example") +foreach ($node in $nodes) { + $node.ParentNode.RemoveChild($node) | Out-Null +} + +# Save modified XML +$xml.Save($outputFile) + +Write-Host "Trimmed XML documentation file saved to: $outputFile" diff --git a/tools/props/AssemblyInfo.props b/tools/props/AssemblyInfo.props index 71488482eb..344862fec4 100644 --- a/tools/props/AssemblyInfo.props +++ b/tools/props/AssemblyInfo.props @@ -6,5 +6,6 @@ Microsoft Corporation Microsoft SqlClient Data Provider © Microsoft Corporation. All rights reserved. + en-US diff --git a/tools/props/Versions.props b/tools/props/Versions.props index b2e08d3531..7b4bc87d81 100644 --- a/tools/props/Versions.props +++ b/tools/props/Versions.props @@ -1,14 +1,26 @@ - + - 6.0.0 + 6.1.1 0 + + $(MdsVersionDefault).$(BuildNumber) - $(AssemblyFileVersion) - - - + 6.0.0.0 + + $(AssemblyFileVersion) $(MdsVersionDefault)-dev $(NugetPackageVersion) @@ -21,59 +33,17 @@ 1.0.0-dev $(SqlServerPackageVersion) - - - 6.0.0-preview1.24226.4 - 4.5.1 - 4.5.5 - 6.0.0 - 6.0.10 - - - - 1.11.4 - 9.0.0 - 7.5.0 - 7.5.0 - 9.0.0 - 9.0.0 - - - - 6.0.0-preview1.24226.4 - 9.0.0 - 1.0.0 - - - - [1.38.0,2.0.0) - [4.5.0,5.0.0) - - - - 0.13.2 - 3.1.6 - 10.0.0-beta.24564.1 - 8.0.0-beta.24123.1 - 6.0.1 - 2.0.8 - 1.0.3 - 17.8.0 - 172.52.0 - 10.50.1600.1 - 160.1000.6 - 5.0.0 - 13.0.1 - 6.0.1 - 4.3.0 - 5.0.0 - 6.0.0 - 6.0.0 - 2.6.3 - 2.5.5 - $(NugetPackageVersion) + + $(NugetPackageVersion) diff --git a/tools/specs/Microsoft.Data.SqlClient.nuspec b/tools/specs/Microsoft.Data.SqlClient.nuspec index 3fd6285c36..bc177256cb 100644 --- a/tools/specs/Microsoft.Data.SqlClient.nuspec +++ b/tools/specs/Microsoft.Data.SqlClient.nuspec @@ -10,42 +10,76 @@ https://aka.ms/sqlclientproject dotnet.png README.md - + The current data provider for SQL Server and Azure SQL databases. This has replaced System.Data.SqlClient. These classes provide access to SQL and encapsulate database-specific protocols, including tabular data stream (TDS). - -Commonly Used Types: -Microsoft.Data.SqlClient.SqlConnection -Microsoft.Data.SqlClient.SqlException -Microsoft.Data.SqlClient.SqlParameter -Microsoft.Data.SqlClient.SqlDataReader -Microsoft.Data.SqlClient.SqlCommand -Microsoft.Data.SqlClient.SqlTransaction -Microsoft.Data.SqlClient.SqlParameterCollection -Microsoft.Data.SqlClient.SqlClientFactory - -When using NuGet 3.x this package requires at least version 3.4. + + Commonly Used Types: + Microsoft.Data.SqlClient.SqlConnection + Microsoft.Data.SqlClient.SqlException + Microsoft.Data.SqlClient.SqlParameter + Microsoft.Data.SqlClient.SqlDataReader + Microsoft.Data.SqlClient.SqlCommand + Microsoft.Data.SqlClient.SqlTransaction + Microsoft.Data.SqlClient.SqlParameterCollection + Microsoft.Data.SqlClient.SqlClientFactory + + When using NuGet 3.x this package requires at least version 3.4. https://go.microsoft.com/fwlink/?linkid=2090501 © Microsoft Corporation. All rights reserved. sqlclient microsoft.data.sqlclient - - - - - + + + + + + + - - + + + + - - + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -54,12 +88,20 @@ When using NuGet 3.x this package requires at least version 3.4. - - + + - - + + + + + + + + + + @@ -71,10 +113,18 @@ When using NuGet 3.x this package requires at least version 3.4. - + + + + + + + + + @@ -95,12 +145,22 @@ When using NuGet 3.x this package requires at least version 3.4. - + - - + + + + + + + + + + + + @@ -114,16 +174,38 @@ When using NuGet 3.x this package requires at least version 3.4. - + + + + + + + + + + + + + + + + + - + + + + + + + diff --git a/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec b/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec index eb9b007d96..968237fd36 100644 --- a/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec +++ b/tools/specs/add-ons/Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyVaultProvider.nuspec @@ -25,16 +25,22 @@ Microsoft.Data.SqlClient.AlwaysEncrypted.AzureKeyStoreProvider.SqlColumnEncrypti sqlclient microsoft.data.sqlclient azurekeyvaultprovider akvprovider alwaysencrypted - - - - + + + + - - - - + + + + + + + + + + diff --git a/tools/targets/TrimDocsForIntelliSense.targets b/tools/targets/TrimDocsForIntelliSense.targets new file mode 100644 index 0000000000..05feb26739 --- /dev/null +++ b/tools/targets/TrimDocsForIntelliSense.targets @@ -0,0 +1,9 @@ + + + + + + + +